mirror of
https://github.com/golang/go
synced 2024-11-23 07:50:05 -07:00
runtime: Fix interaction between Goexit and defers
When running defers, we must check whether the defer has already been marked as started so we don't run it twice. Fixes #8774. LGTM=rsc R=rsc CC=golang-codereviews https://golang.org/cl/142280044
This commit is contained in:
parent
651bb8e026
commit
0306478fe5
@ -412,3 +412,91 @@ func main() {
|
||||
runtime.Breakpoint()
|
||||
}
|
||||
`
|
||||
|
||||
func TestGoexitInPanic(t *testing.T) {
|
||||
// see issue 8774: this code used to trigger an infinite recursion
|
||||
output := executeTest(t, goexitInPanicSource, nil)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
const goexitInPanicSource = `
|
||||
package main
|
||||
import "runtime"
|
||||
func main() {
|
||||
go func() {
|
||||
defer func() {
|
||||
runtime.Goexit()
|
||||
}()
|
||||
panic("hello")
|
||||
}()
|
||||
runtime.Goexit()
|
||||
}
|
||||
`
|
||||
|
||||
func TestPanicAfterGoexit(t *testing.T) {
|
||||
// an uncaught panic should still work after goexit
|
||||
output := executeTest(t, panicAfterGoexitSource, nil)
|
||||
want := "panic: hello"
|
||||
if !strings.HasPrefix(output, want) {
|
||||
t.Fatalf("output does not start with %q:\n%s", want, output)
|
||||
}
|
||||
}
|
||||
|
||||
const panicAfterGoexitSource = `
|
||||
package main
|
||||
import "runtime"
|
||||
func main() {
|
||||
defer func() {
|
||||
panic("hello")
|
||||
}()
|
||||
runtime.Goexit()
|
||||
}
|
||||
`
|
||||
|
||||
func TestRecoveredPanicAfterGoexit(t *testing.T) {
|
||||
output := executeTest(t, recoveredPanicAfterGoexitSource, nil)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
const recoveredPanicAfterGoexitSource = `
|
||||
package main
|
||||
import "runtime"
|
||||
func main() {
|
||||
defer func() {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r == nil {
|
||||
panic("bad recover")
|
||||
}
|
||||
}()
|
||||
panic("hello")
|
||||
}()
|
||||
runtime.Goexit()
|
||||
}
|
||||
`
|
||||
|
||||
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()
|
||||
}
|
||||
|
@ -247,11 +247,27 @@ func deferreturn(arg0 uintptr) {
|
||||
// If all other goroutines exit, the program crashes.
|
||||
func Goexit() {
|
||||
// Run all deferred functions for the current goroutine.
|
||||
// This code is similar to gopanic, see that implementation
|
||||
// for detailed comments.
|
||||
gp := getg()
|
||||
for gp._defer != nil {
|
||||
for {
|
||||
d := gp._defer
|
||||
if d == nil {
|
||||
break
|
||||
}
|
||||
if d.started {
|
||||
if d._panic != nil {
|
||||
d._panic.aborted = true
|
||||
}
|
||||
gp._defer = d.link
|
||||
freedefer(d)
|
||||
continue
|
||||
}
|
||||
d.started = true
|
||||
reflectcall(unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))
|
||||
if gp._defer != d {
|
||||
gothrow("bad defer entry in Goexit")
|
||||
}
|
||||
gp._defer = d.link
|
||||
freedefer(d)
|
||||
// Note: we ignore recovers here because Goexit isn't a panic
|
||||
|
Loading…
Reference in New Issue
Block a user