@@ -12,8 +12,6 @@ package errgroup
12
12
import (
13
13
"context"
14
14
"fmt"
15
- "runtime"
16
- "runtime/debug"
17
15
"sync"
18
16
)
19
17
@@ -33,10 +31,6 @@ type Group struct {
33
31
34
32
errOnce sync.Once
35
33
err error
36
-
37
- mu sync.Mutex
38
- panicValue any // = PanicError | PanicValue; non-nil if some Group.Go goroutine panicked.
39
- abnormal bool // some Group.Go goroutine terminated abnormally (panic or goexit).
40
34
}
41
35
42
36
func (g * Group ) done () {
@@ -56,22 +50,13 @@ func WithContext(ctx context.Context) (*Group, context.Context) {
56
50
return & Group {cancel : cancel }, ctx
57
51
}
58
52
59
- // Wait blocks until all function calls from the Go method have returned
60
- // normally, then returns the first non-nil error (if any) from them.
61
- //
62
- // If any of the calls panics, Wait panics with a [PanicValue];
63
- // and if any of them calls [runtime.Goexit], Wait calls runtime.Goexit.
53
+ // Wait blocks until all function calls from the Go method have returned, then
54
+ // returns the first non-nil error (if any) from them.
64
55
func (g * Group ) Wait () error {
65
56
g .wg .Wait ()
66
57
if g .cancel != nil {
67
58
g .cancel (g .err )
68
59
}
69
- if g .panicValue != nil {
70
- panic (g .panicValue )
71
- }
72
- if g .abnormal {
73
- runtime .Goexit ()
74
- }
75
60
return g .err
76
61
}
77
62
@@ -81,53 +66,31 @@ func (g *Group) Wait() error {
81
66
// It blocks until the new goroutine can be added without the number of
82
67
// goroutines in the group exceeding the configured limit.
83
68
//
84
- // The first goroutine in the group that returns a non-nil error, panics, or
85
- // invokes [runtime.Goexit] will cancel the associated Context, if any.
69
+ // The first goroutine in the group that returns a non-nil error will
70
+ // cancel the associated Context, if any. The error will be returned
71
+ // by Wait.
86
72
func (g * Group ) Go (f func () error ) {
87
73
if g .sem != nil {
88
74
g .sem <- token {}
89
75
}
90
76
91
- g .add (f )
92
- }
93
-
94
- func (g * Group ) add (f func () error ) {
95
77
g .wg .Add (1 )
96
78
go func () {
97
79
defer g .done ()
98
- normalReturn := false
99
- defer func () {
100
- if normalReturn {
101
- return
102
- }
103
- v := recover ()
104
- g .mu .Lock ()
105
- defer g .mu .Unlock ()
106
- if ! g .abnormal {
107
- if g .cancel != nil {
108
- g .cancel (g .err )
109
- }
110
- g .abnormal = true
111
- }
112
- if v != nil && g .panicValue == nil {
113
- switch v := v .(type ) {
114
- case error :
115
- g .panicValue = PanicError {
116
- Recovered : v ,
117
- Stack : debug .Stack (),
118
- }
119
- default :
120
- g .panicValue = PanicValue {
121
- Recovered : v ,
122
- Stack : debug .Stack (),
123
- }
124
- }
125
- }
126
- }()
127
80
128
- err := f ()
129
- normalReturn = true
130
- if err != nil {
81
+ // It is tempting to propagate panics from f()
82
+ // up to the goroutine that calls Wait, but
83
+ // it creates more problems than it solves:
84
+ // - it delays panics arbitrarily,
85
+ // making bugs harder to detect;
86
+ // - it turns f's panic stack into a mere value,
87
+ // hiding it from crash-monitoring tools;
88
+ // - it risks deadlocks that hide the panic entirely,
89
+ // if f's panic leaves the program in a state
90
+ // that prevents the Wait call from being reached.
91
+ // See #53757, #74275, #74304, #74306.
92
+
93
+ if err := f (); err != nil {
131
94
g .errOnce .Do (func () {
132
95
g .err = err
133
96
if g .cancel != nil {
@@ -152,7 +115,19 @@ func (g *Group) TryGo(f func() error) bool {
152
115
}
153
116
}
154
117
155
- g .add (f )
118
+ g .wg .Add (1 )
119
+ go func () {
120
+ defer g .done ()
121
+
122
+ if err := f (); err != nil {
123
+ g .errOnce .Do (func () {
124
+ g .err = err
125
+ if g .cancel != nil {
126
+ g .cancel (g .err )
127
+ }
128
+ })
129
+ }
130
+ }()
156
131
return true
157
132
}
158
133
@@ -174,34 +149,3 @@ func (g *Group) SetLimit(n int) {
174
149
}
175
150
g .sem = make (chan token , n )
176
151
}
177
-
178
- // PanicError wraps an error recovered from an unhandled panic
179
- // when calling a function passed to Go or TryGo.
180
- type PanicError struct {
181
- Recovered error
182
- Stack []byte // result of call to [debug.Stack]
183
- }
184
-
185
- func (p PanicError ) Error () string {
186
- if len (p .Stack ) > 0 {
187
- return fmt .Sprintf ("recovered from errgroup.Group: %v\n %s" , p .Recovered , p .Stack )
188
- }
189
- return fmt .Sprintf ("recovered from errgroup.Group: %v" , p .Recovered )
190
- }
191
-
192
- func (p PanicError ) Unwrap () error { return p .Recovered }
193
-
194
- // PanicValue wraps a value that does not implement the error interface,
195
- // recovered from an unhandled panic when calling a function passed to Go or
196
- // TryGo.
197
- type PanicValue struct {
198
- Recovered any
199
- Stack []byte // result of call to [debug.Stack]
200
- }
201
-
202
- func (p PanicValue ) String () string {
203
- if len (p .Stack ) > 0 {
204
- return fmt .Sprintf ("recovered from errgroup.Group: %v\n %s" , p .Recovered , p .Stack )
205
- }
206
- return fmt .Sprintf ("recovered from errgroup.Group: %v" , p .Recovered )
207
- }
0 commit comments