@@ -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,46 @@ 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
+ // Rebind idx inside the loop so that each goroutine captures
61
+ // a distinct copy of the index. Without this, all goroutines
62
+ // may observe the same final idx value due to closure capture.
63
+ idx := idx
64
+
65
+ // Snapshot s[idx] now so the goroutine sees the intended value
66
+ // even if the caller mutates the slice later. This is a shallow
67
+ // copy only, if V contains pointers, their contents can still
68
+ // change.
69
+ v := s [idx ]
70
+
53
71
errGroup .Go (func () error {
54
- err := f (ctx , s [idx ])
72
+ // If already canceled, skip work without signaling a
73
+ // error.
74
+ select {
75
+ case <- groupCtx .Done ():
76
+ return nil
77
+ default :
78
+ }
79
+
80
+ err := f (groupCtx , v )
55
81
if err != nil {
56
- instanceErrorsMutex .Lock ()
82
+ instanceErrorsMu .Lock ()
57
83
instanceErrors [idx ] = err
58
- instanceErrorsMutex .Unlock ()
84
+ instanceErrorsMu .Unlock ()
59
85
}
60
86
61
87
// Avoid returning an error here, as that would cancel
62
88
// the errGroup and terminate all slice element
63
89
// processing instances. Instead, collect the error and
64
- // return it later.
90
+ // return them later.
65
91
return nil
66
92
})
67
93
}
@@ -71,8 +97,7 @@ func ParSliceErrCollect[V any](ctx context.Context, s []V,
71
97
// The goroutines that are executing in parallel should not return an
72
98
// error, but the Wait call may return an error if the context is
73
99
// canceled or timed out.
74
- err := errGroup .Wait ()
75
- if err != nil {
100
+ if err := errGroup .Wait (); err != nil {
76
101
return nil , fmt .Errorf ("failed to wait on error group in " +
77
102
"ParSliceErrorCollect: %w" , err )
78
103
}
0 commit comments