Skip to content

Commit a5a5e2c

Browse files
committed
runtime: make sure to remove open-coded defer entries in all cases after a recover
We add entries to the defer list at panic/goexit time on-the-fly for frames with open-coded defers. We do this so that we can correctly process open-coded defers and non-open-coded defers in the correct order during panics/goexits. But we need to remove entries for open-coded defers from the defer list when there is a recover, since those entries may never get removed otherwise and will get stale, since their corresponding defers may now be processed normally (inline). This bug here is that we were only removing higher-up stale entries during a recover if all defers in the current frame were done. But we could have more defers in the current frame (as the new test case shows). In this case, we need to leave the current defer entry around for use by deferreturn, but still remove any stale entries further along the chain. For bug 43921, simple change that we should abort the removal loop for any defer entry that is started (i.e. in process by a still not-recovered outer panic), even if it is not an open-coded defer. This change does not fix bug 43920, which looks to be a more complex fix. Fixes #43882 Fixes #43921 Change-Id: Ie05b2fa26973aa26b25c8899a2abc916090ee4f5 Reviewed-on: https://go-review.googlesource.com/c/go/+/286712 Run-TryBot: Dan Scales <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Matthew Dempsky <[email protected]> Reviewed-by: Keith Randall <[email protected]> Trust: Dan Scales <[email protected]>
1 parent 8cfa019 commit a5a5e2c

File tree

4 files changed

+113
-29
lines changed

4 files changed

+113
-29
lines changed

src/runtime/crash_test.go

+12
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,18 @@ func TestRecursivePanic4(t *testing.T) {
294294

295295
}
296296

297+
func TestRecursivePanic5(t *testing.T) {
298+
output := runTestProg(t, "testprog", "RecursivePanic5")
299+
want := `first panic
300+
second panic
301+
panic: third panic
302+
`
303+
if !strings.HasPrefix(output, want) {
304+
t.Fatalf("output does not start with %q:\n%s", want, output)
305+
}
306+
307+
}
308+
297309
func TestGoexitCrash(t *testing.T) {
298310
// External linking brings in cgo, causing deadlock detection not working.
299311
testenv.MustInternalLink(t)

src/runtime/defer_test.go

+28
Original file line numberDiff line numberDiff line change
@@ -410,3 +410,31 @@ func rec1(max int) {
410410
rec1(max - 1)
411411
}
412412
}
413+
414+
func TestIssue43921(t *testing.T) {
415+
defer func() {
416+
expect(t, 1, recover())
417+
}()
418+
func() {
419+
// Prevent open-coded defers
420+
for {
421+
defer func() {}()
422+
break
423+
}
424+
425+
defer func() {
426+
defer func() {
427+
expect(t, 4, recover())
428+
}()
429+
panic(4)
430+
}()
431+
panic(1)
432+
433+
}()
434+
}
435+
436+
func expect(t *testing.T, n int, err interface{}) {
437+
if n != err {
438+
t.Fatalf("have %v, want %v", err, n)
439+
}
440+
}

src/runtime/panic.go

+34-29
Original file line numberDiff line numberDiff line change
@@ -1000,37 +1000,42 @@ func gopanic(e interface{}) {
10001000
}
10011001
atomic.Xadd(&runningPanicDefers, -1)
10021002

1003-
if done {
1004-
// Remove any remaining non-started, open-coded
1005-
// defer entries after a recover, since the
1006-
// corresponding defers will be executed normally
1007-
// (inline). Any such entry will become stale once
1008-
// we run the corresponding defers inline and exit
1009-
// the associated stack frame.
1010-
d := gp._defer
1011-
var prev *_defer
1012-
for d != nil {
1013-
if d.openDefer {
1014-
if d.started {
1015-
// This defer is started but we
1016-
// are in the middle of a
1017-
// defer-panic-recover inside of
1018-
// it, so don't remove it or any
1019-
// further defer entries
1020-
break
1021-
}
1022-
if prev == nil {
1023-
gp._defer = d.link
1024-
} else {
1025-
prev.link = d.link
1026-
}
1027-
newd := d.link
1028-
freedefer(d)
1029-
d = newd
1003+
// Remove any remaining non-started, open-coded
1004+
// defer entries after a recover, since the
1005+
// corresponding defers will be executed normally
1006+
// (inline). Any such entry will become stale once
1007+
// we run the corresponding defers inline and exit
1008+
// the associated stack frame.
1009+
d := gp._defer
1010+
var prev *_defer
1011+
if !done {
1012+
// Skip our current frame, if not done. It is
1013+
// needed to complete any remaining defers in
1014+
// deferreturn()
1015+
prev = d
1016+
d = d.link
1017+
}
1018+
for d != nil {
1019+
if d.started {
1020+
// This defer is started but we
1021+
// are in the middle of a
1022+
// defer-panic-recover inside of
1023+
// it, so don't remove it or any
1024+
// further defer entries
1025+
break
1026+
}
1027+
if d.openDefer {
1028+
if prev == nil {
1029+
gp._defer = d.link
10301030
} else {
1031-
prev = d
1032-
d = d.link
1031+
prev.link = d.link
10331032
}
1033+
newd := d.link
1034+
freedefer(d)
1035+
d = newd
1036+
} else {
1037+
prev = d
1038+
d = d.link
10341039
}
10351040
}
10361041

src/runtime/testdata/testprog/deadlock.go

+39
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ func init() {
2525
register("RecursivePanic2", RecursivePanic2)
2626
register("RecursivePanic3", RecursivePanic3)
2727
register("RecursivePanic4", RecursivePanic4)
28+
register("RecursivePanic5", RecursivePanic5)
2829
register("GoexitExit", GoexitExit)
2930
register("GoNil", GoNil)
3031
register("MainGoroutineID", MainGoroutineID)
@@ -160,6 +161,44 @@ func RecursivePanic4() {
160161
panic("first panic")
161162
}
162163

164+
// Test case where we have an open-coded defer higher up the stack (in two), and
165+
// in the current function (three) we recover in a defer while we still have
166+
// another defer to be processed.
167+
func RecursivePanic5() {
168+
one()
169+
panic("third panic")
170+
}
171+
172+
//go:noinline
173+
func one() {
174+
two()
175+
}
176+
177+
//go:noinline
178+
func two() {
179+
defer func() {
180+
}()
181+
182+
three()
183+
}
184+
185+
//go:noinline
186+
func three() {
187+
defer func() {
188+
}()
189+
190+
defer func() {
191+
fmt.Println(recover())
192+
}()
193+
194+
defer func() {
195+
fmt.Println(recover())
196+
panic("second panic")
197+
}()
198+
199+
panic("first panic")
200+
}
201+
163202
func GoexitExit() {
164203
println("t1")
165204
go func() {

0 commit comments

Comments
 (0)