-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtaskctx.go
More file actions
102 lines (93 loc) · 2.81 KB
/
Copy pathtaskctx.go
File metadata and controls
102 lines (93 loc) · 2.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package main
import (
"fmt"
"io"
"runtime"
"strconv"
"strings"
"sync"
)
// Goroutine-local task output routing.
//
// Why: install handlers in custom.go and post.go are deeply nested calls
// that use fmt.Printf/Println directly and pass CmdOpts to runCmd. To route
// their output into a per-task buffer (so parallel workers don't interleave
// on os.Stdout), we'd otherwise need to thread an io.Writer through every
// signature — ~30 call sites of churn including tests.
//
// Instead we keep a sync.Map keyed by goroutine id. The parallel orchestrator
// associates a taskOutput with its worker goroutine before invoking the
// handler; helpers below check the map and route output to the active task
// when present, falling back to direct stdout otherwise. Sequential callers
// observe no behavior change.
//
// goid() uses runtime.Stack — a small hack, but stable and idiomatic for
// goroutine-local state where context.Context threading would dwarf the
// surrounding work.
var activeTaskByGoroutine sync.Map // map[uint64]*taskOutput
func goid() uint64 {
var buf [64]byte
n := runtime.Stack(buf[:], false)
s := string(buf[:n])
s = strings.TrimPrefix(s, "goroutine ")
end := strings.IndexByte(s, ' ')
if end < 0 {
return 0
}
id, _ := strconv.ParseUint(s[:end], 10, 64)
return id
}
// withTaskOutput pins tOut to the current goroutine for the duration of fn,
// then unpins. Re-entrant calls overwrite the previous binding and restore
// it on return. A nil tOut is treated as "no binding" (sequential mode).
func withTaskOutput(tOut *taskOutput, fn func()) {
if tOut == nil {
fn()
return
}
id := goid()
prev, hadPrev := activeTaskByGoroutine.Load(id)
activeTaskByGoroutine.Store(id, tOut)
defer func() {
if hadPrev {
activeTaskByGoroutine.Store(id, prev)
} else {
activeTaskByGoroutine.Delete(id)
}
}()
fn()
}
// currentTask returns the taskOutput pinned to the current goroutine, or
// nil if none. Cheap enough to call per print (~microseconds).
func currentTask() *taskOutput {
v, ok := activeTaskByGoroutine.Load(goid())
if !ok {
return nil
}
return v.(*taskOutput)
}
// taskPrintf routes via the active task (if any) or directly to stdout.
func taskPrintf(format string, args ...any) {
if t := currentTask(); t != nil {
t.Printf(format, args...)
return
}
fmt.Printf(format, args...)
}
// taskPrintln routes via the active task (if any) or directly to stdout.
func taskPrintln(args ...any) {
if t := currentTask(); t != nil {
t.Println(args...)
return
}
fmt.Println(args...)
}
// taskOut returns the io.Writer that runCmd / runShell should target via
// CmdOpts.Out for the active task. Returns nil when there is no active task,
// which preserves runCmd's default streamed-to-stdout behavior.
func taskOut() io.Writer {
if t := currentTask(); t != nil {
return t.Writer()
}
return nil
}