@@ -12,8 +12,6 @@ package errgroup
1212import (
1313 "context"
1414 "fmt"
15- "runtime"
16- "runtime/debug"
1715 "sync"
1816)
1917
@@ -33,10 +31,6 @@ type Group struct {
3331
3432 errOnce sync.Once
3533 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).
4034}
4135
4236func (g * Group ) done () {
@@ -56,22 +50,13 @@ func WithContext(ctx context.Context) (*Group, context.Context) {
5650 return & Group {cancel : cancel }, ctx
5751}
5852
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.
6455func (g * Group ) Wait () error {
6556 g .wg .Wait ()
6657 if g .cancel != nil {
6758 g .cancel (g .err )
6859 }
69- if g .panicValue != nil {
70- panic (g .panicValue )
71- }
72- if g .abnormal {
73- runtime .Goexit ()
74- }
7560 return g .err
7661}
7762
@@ -81,53 +66,31 @@ func (g *Group) Wait() error {
8166// It blocks until the new goroutine can be added without the number of
8267// goroutines in the group exceeding the configured limit.
8368//
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.
8672func (g * Group ) Go (f func () error ) {
8773 if g .sem != nil {
8874 g .sem <- token {}
8975 }
9076
91- g .add (f )
92- }
93-
94- func (g * Group ) add (f func () error ) {
9577 g .wg .Add (1 )
9678 go func () {
9779 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- }()
12780
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 {
13194 g .errOnce .Do (func () {
13295 g .err = err
13396 if g .cancel != nil {
@@ -152,7 +115,19 @@ func (g *Group) TryGo(f func() error) bool {
152115 }
153116 }
154117
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+ }()
156131 return true
157132}
158133
@@ -174,34 +149,3 @@ func (g *Group) SetLimit(n int) {
174149 }
175150 g .sem = make (chan token , n )
176151}
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