@@ -26,7 +26,12 @@ func ParSlice[V any](ctx context.Context, s []V, f ErrFunc[V]) error {
26
26
errGroup .SetLimit (runtime .GOMAXPROCS (0 ))
27
27
28
28
for _ , v := range s {
29
+ // Snapshot v now so the goroutine sees the intended value
30
+ // even if the caller mutates the slice s later. This is a
31
+ // shallow copy only, if V contains pointers, their contents can
32
+ // still change.
29
33
v := v
34
+
30
35
errGroup .Go (func () error {
31
36
return f (ctx , v )
32
37
})
@@ -43,25 +48,41 @@ func ParSlice[V any](ctx context.Context, s []V, f ErrFunc[V]) error {
43
48
func ParSliceErrCollect [V any ](ctx context.Context , s []V ,
44
49
f ErrFunc [V ]) (map [int ]error , error ) {
45
50
46
- errGroup , ctx := errgroup .WithContext (ctx )
51
+ errGroup , groupCtx := errgroup .WithContext (ctx )
47
52
errGroup .SetLimit (runtime .GOMAXPROCS (0 ))
48
53
49
- var instanceErrorsMutex sync.Mutex
50
- instanceErrors := make (map [int ]error , len (s ))
54
+ var (
55
+ instanceErrorsMu sync.Mutex
56
+ instanceErrors = make (map [int ]error , len (s ))
57
+ )
51
58
52
59
for idx := range s {
60
+ // Snapshot s[idx] now so the goroutine sees the intended value
61
+ // even if the caller mutates the slice later. This is a shallow
62
+ // copy only, if V contains pointers, their contents can still
63
+ // change.
64
+ v := s [idx ]
65
+
53
66
errGroup .Go (func () error {
54
- err := f (ctx , s [idx ])
67
+ // If already canceled, skip work without signaling an
68
+ // error.
69
+ select {
70
+ case <- groupCtx .Done ():
71
+ return nil
72
+ default :
73
+ }
74
+
75
+ err := f (groupCtx , v )
55
76
if err != nil {
56
- instanceErrorsMutex .Lock ()
77
+ instanceErrorsMu .Lock ()
57
78
instanceErrors [idx ] = err
58
- instanceErrorsMutex .Unlock ()
79
+ instanceErrorsMu .Unlock ()
59
80
}
60
81
61
- // Avoid returning an error here, as that would cancel
62
- // the errGroup and terminate all slice element
63
- // processing instances . Instead, collect the error and
64
- // return it later .
82
+ // Do not return an error here. If we did, errGroup
83
+ // would cancel the context and stop all other element
84
+ // processors . Instead, record the error locally
85
+ // and return it after all goroutines finish .
65
86
return nil
66
87
})
67
88
}
@@ -71,11 +92,16 @@ func ParSliceErrCollect[V any](ctx context.Context, s []V,
71
92
// The goroutines that are executing in parallel should not return an
72
93
// error, but the Wait call may return an error if the context is
73
94
// canceled or timed out.
74
- err := errGroup .Wait ()
75
- if err != nil {
95
+ if err := errGroup .Wait (); err != nil {
76
96
return nil , fmt .Errorf ("failed to wait on error group in " +
77
97
"ParSliceErrorCollect: %w" , err )
78
98
}
79
99
100
+ // If the caller's context was canceled or timed out, surface that.
101
+ // Return whatever per-item errors were collected before cancellation.
102
+ if err := ctx .Err (); err != nil {
103
+ return instanceErrors , err
104
+ }
105
+
80
106
return instanceErrors , nil
81
107
}
0 commit comments