-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbatch.go
More file actions
85 lines (79 loc) · 2.78 KB
/
Copy pathbatch.go
File metadata and controls
85 lines (79 loc) · 2.78 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
package processkit
import (
"context"
"errors"
"reflect"
"sync"
)
// WaitAny waits for whichever of the given processes exits first, returning its
// index, its [Outcome], and any wait error. It returns early with ctx's error if
// the context is done first. The processes are only observed — the losers stay
// usable afterwards.
func WaitAny(ctx context.Context, procs ...*RunningProcess) (int, Outcome, error) {
if len(procs) == 0 {
return 0, Outcome{}, errors.New("processkit: WaitAny needs at least one process")
}
cases := make([]reflect.SelectCase, 0, len(procs)+1)
for _, p := range procs {
cases = append(cases, reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(p.done)})
}
cases = append(cases, reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(ctx.Done())})
chosen, _, _ := reflect.Select(cases)
if chosen == len(procs) { // the ctx.Done() case
return 0, Outcome{}, ctx.Err()
}
// Safe to read outcome/waitErr unguarded: reap writes them before closing done,
// and we only reach here by receiving that close.
return chosen, procs[chosen].outcome, procs[chosen].waitErr
}
// WaitAll waits for every process to exit, returning their [Outcome]s in input
// order. It returns early with ctx's error if the context is done first, or with
// the first process's wait error — in which case the returned slice is nil (a
// wait error is a rare reap failure, not a non-zero exit, which is carried in the
// [Outcome]).
func WaitAll(ctx context.Context, procs ...*RunningProcess) ([]Outcome, error) {
outcomes := make([]Outcome, len(procs))
for i, p := range procs {
select {
case <-p.done:
if p.waitErr != nil {
return nil, p.waitErr
}
outcomes[i] = p.outcome
case <-ctx.Done():
return nil, ctx.Err()
}
}
return outcomes, nil
}
// BatchOutput is one command's independent result from [OutputAll]: either a
// captured Result (any exit code) or an error (spawn failure, cancellation).
type BatchOutput struct {
Result *Result
Err error
}
// OutputAll runs every command to completion and captures each result, with at
// most concurrency runs in flight at once (so fanning out hundreds of commands
// can't exhaust file descriptors or the process table). It is collect-all: a
// non-zero exit never short-circuits the batch — each element is independent, in
// input order.
func OutputAll(ctx context.Context, cmds []*Cmd, concurrency int) []BatchOutput {
if concurrency < 1 {
concurrency = 1
}
out := make([]BatchOutput, len(cmds))
sem := make(chan struct{}, concurrency)
var wg sync.WaitGroup
for i, c := range cmds {
wg.Add(1)
go func(i int, c *Cmd) {
defer wg.Done()
sem <- struct{}{}
defer func() { <-sem }()
res, err := c.Output(ctx)
out[i] = BatchOutput{Result: res, Err: err}
}(i, c)
}
wg.Wait()
return out
}