Skip to content

Commit fb39447

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 8cf7a0b commit fb39447

File tree

2 files changed

+56
-1
lines changed

2 files changed

+56
-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: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@
55
package sync_test
66

77
import (
8+
"bytes"
9+
"os"
10+
"os/exec"
11+
"runtime"
12+
"strings"
13+
"sync"
814
. "sync"
915
"sync/atomic"
1016
"testing"
@@ -110,6 +116,44 @@ func TestWaitGroupGo(t *testing.T) {
110116
}
111117
}
112118

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

0 commit comments

Comments
 (0)