Commit 0306478f authored by Keith Randall's avatar Keith Randall

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
parent 651bb8e0
......@@ -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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment