Skip to content

Commit eedf805

Browse files
committed
sync: repanic when f() panics for WaitGroup.Go
This is a copy-paste of #76126 (comment) by adonovan. Fixes #76126 Fixes #74702
1 parent 9f3a108 commit eedf805

File tree

2 files changed

+52
-1
lines changed

2 files changed

+52
-1
lines changed

src/sync/waitgroup.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,18 @@ func (wg *WaitGroup) Wait() {
236236
func (wg *WaitGroup) Go(f func()) {
237237
wg.Add(1)
238238
go func() {
239-
defer wg.Done()
239+
defer func() {
240+
if x := recover(); x != nil {
241+
// Don't call Done as it may cause Wait to unblock,
242+
// so that the main goroutine races with the runtime.fatal
243+
// resulting from unhandled panic.
244+
panic(x)
245+
}
246+
247+
// f completed normally, or abruptly using goexit.
248+
// Either way, decrement the semaphore.
249+
wg.Done()
250+
}()
240251
f()
241252
}()
242253
}

src/sync/waitgroup_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55
package sync_test
66

77
import (
8+
"bytes"
9+
"os"
10+
"os/exec"
11+
"strings"
12+
"sync"
813
. "sync"
914
"sync/atomic"
1015
"testing"
@@ -110,6 +115,41 @@ func TestWaitGroupGo(t *testing.T) {
110115
}
111116
}
112117

118+
func TestIssue76126(t *testing.T) {
119+
// Call child in a child process
120+
// and inspect its failure message.
121+
cmd := exec.Command(os.Args[0],
122+
append(os.Args[1:], "-test.run", "^TestIssue76126Child$/^child$")...)
123+
cmd.Env = append(os.Environ(),
124+
"ENTRYPOINT=child")
125+
buf := new(bytes.Buffer)
126+
cmd.Stderr = buf
127+
cmd.Run() // ignore error
128+
129+
got := buf.String()
130+
if strings.Contains(got, "panic: test") {
131+
// ok
132+
} else {
133+
t.Errorf("missing panic: test\n%s", got)
134+
}
135+
}
136+
137+
func TestIssue76126Child(t *testing.T) {
138+
t.Run("child", func(t *testing.T) {
139+
switch os.Getenv("ENTRYPOINT") {
140+
case "child":
141+
default:
142+
t.Skip("not child")
143+
}
144+
var wg sync.WaitGroup
145+
wg.Go(func() {
146+
panic("test")
147+
})
148+
wg.Wait() // process should terminate here
149+
panic("Wait returned") // must not be reached
150+
})
151+
}
152+
113153
func BenchmarkWaitGroupUncontended(b *testing.B) {
114154
type PaddedWaitGroup struct {
115155
WaitGroup

0 commit comments

Comments
 (0)