Skip to content

Commit 24e36fb

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 24e36fb

File tree

2 files changed

+53
-1
lines changed

2 files changed

+53
-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: 41 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,42 @@ func TestWaitGroupGo(t *testing.T) {
110115
}
111116
}
112117

118+
// We define our own main function so that portions of
119+
// some tests can run in a separate (child) process.
120+
func TestMain(m *testing.M) {
121+
switch os.Getenv("ENTRYPOINT") {
122+
case "child":
123+
child()
124+
default:
125+
os.Exit(m.Run())
126+
}
127+
}
128+
func TestIssue76126(t *testing.T) {
129+
// Call child in a child process
130+
// and inspect its failure message.
131+
cmd := exec.Command(os.Args[0], os.Args[1:]...)
132+
cmd.Env = append(os.Environ(),
133+
"ENTRYPOINT=child")
134+
buf := new(bytes.Buffer)
135+
cmd.Stdout = buf
136+
cmd.Stderr = buf
137+
cmd.Run() // ignore error
138+
got := buf.String()
139+
if strings.Contains(got, "panic: test") {
140+
// ok
141+
} else {
142+
t.Errorf("got: %s", buf)
143+
}
144+
}
145+
func child() {
146+
var wg sync.WaitGroup
147+
wg.Go(func() {
148+
panic("test")
149+
})
150+
wg.Wait() // process should terminate here
151+
panic("Wait returned") // must not be reached
152+
}
153+
113154
func BenchmarkWaitGroupUncontended(b *testing.B) {
114155
type PaddedWaitGroup struct {
115156
WaitGroup

0 commit comments

Comments
 (0)