mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
runtime: ensure that Goexit cannot be aborted by a recursive panic/recover
When we do a successful recover of a panic, we resume normal execution by returning from the frame that had the deferred call that did the recover (after executing any remaining deferred calls in that frame). However, suppose we have called runtime.Goexit and there is a panic during one of the deferred calls run by the Goexit. Further assume that there is a deferred call in the frame of the Goexit or a parent frame that does a recover. Then the recovery process will actually resume normal execution above the Goexit frame and hence abort the Goexit. We will not terminate the thread as expected, but continue running in the frame above the Goexit. To fix this, we explicitly create a _panic object for a Goexit call. We then change the "abort" behavior for Goexits, but not panics. After a recovery, if the top-level panic is actually a Goexit that is marked to be aborted, then we return to the Goexit defer-processing loop, so that the Goexit is not actually aborted. Actual code changes are just panic.go, runtime2.go, and funcid.go. Adjusted the test related to the new Goexit behavior (TestRecoverBeforePanicAfterGoexit) and added several new tests of aborted panics (whose behavior has not changed). Fixes #29226 Change-Id: Ib13cb0074f5acc2567a28db7ca6912cfc47eecb5 Reviewed-on: https://go-review.googlesource.com/c/go/+/200081 Run-TryBot: Dan Scales <danscales@google.com> Reviewed-by: Keith Randall <khr@golang.org>
This commit is contained in:
parent
a8fc82f77a
commit
7dcd343ed6
8 changed files with 232 additions and 34 deletions
|
|
@ -284,6 +284,17 @@ func TestRecursivePanic3(t *testing.T) {
|
|||
|
||||
}
|
||||
|
||||
func TestRecursivePanic4(t *testing.T) {
|
||||
output := runTestProg(t, "testprog", "RecursivePanic4")
|
||||
want := `panic: first panic [recovered]
|
||||
panic: second panic
|
||||
`
|
||||
if !strings.HasPrefix(output, want) {
|
||||
t.Fatalf("output does not start with %q:\n%s", want, output)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGoexitCrash(t *testing.T) {
|
||||
output := runTestProg(t, "testprog", "GoexitExit")
|
||||
want := "no goroutines (main called runtime.Goexit) - deadlock!"
|
||||
|
|
@ -415,23 +426,21 @@ func TestRecoveredPanicAfterGoexit(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRecoverBeforePanicAfterGoexit(t *testing.T) {
|
||||
// 1. defer a function that recovers
|
||||
// 2. defer a function that panics
|
||||
// 3. call goexit
|
||||
// Goexit should run the #2 defer. Its panic
|
||||
// should be caught by the #1 defer, and execution
|
||||
// should resume in the caller. Like the Goexit
|
||||
// never happened!
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r == nil {
|
||||
panic("bad recover")
|
||||
}
|
||||
}()
|
||||
defer func() {
|
||||
panic("hello")
|
||||
}()
|
||||
runtime.Goexit()
|
||||
t.Parallel()
|
||||
output := runTestProg(t, "testprog", "RecoverBeforePanicAfterGoexit")
|
||||
want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!"
|
||||
if !strings.HasPrefix(output, want) {
|
||||
t.Fatalf("output does not start with %q:\n%s", want, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecoverBeforePanicAfterGoexit2(t *testing.T) {
|
||||
t.Parallel()
|
||||
output := runTestProg(t, "testprog", "RecoverBeforePanicAfterGoexit2")
|
||||
want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!"
|
||||
if !strings.HasPrefix(output, want) {
|
||||
t.Fatalf("output does not start with %q:\n%s", want, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNetpollDeadlock(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue