Commit 2d86f494 authored by Ian Lance Taylor's avatar Ian Lance Taylor

runtime: delay exiting while panic is running deferred functions

Try to avoid a race between the main goroutine exiting and a panic
occurring. Don't try too hard, to avoid hanging.

Updates #3934
Fixes #20018

Change-Id: I57a02b6d795d2a61f1cadd137ce097145280ece7
Reviewed-on: https://go-review.googlesource.com/41052Reviewed-by: default avatarAustin Clements <austin@google.com>
parent 3c745d75
...@@ -568,3 +568,32 @@ func TestPanicInlined(t *testing.T) { ...@@ -568,3 +568,32 @@ func TestPanicInlined(t *testing.T) {
pt := new(point) pt := new(point)
pt.negate() pt.negate()
} }
// Test for issues #3934 and #20018.
// We want to delay exiting until a panic print is complete.
func TestPanicRace(t *testing.T) {
testenv.MustHaveGoRun(t)
exe, err := buildTestProg(t, "testprog")
if err != nil {
t.Fatal(err)
}
got, err := testEnv(exec.Command(exe, "PanicRace")).CombinedOutput()
if err == nil {
t.Error("program exited successfully, should have failed")
}
t.Logf("%s\n", got)
wants := []string{
"panic: crash",
"PanicRace",
"created by ",
}
for _, want := range wants {
if !bytes.Contains(got, []byte(want)) {
t.Errorf("did not find expected string %q", want)
}
}
}
...@@ -456,6 +456,8 @@ func gopanic(e interface{}) { ...@@ -456,6 +456,8 @@ func gopanic(e interface{}) {
p.link = gp._panic p.link = gp._panic
gp._panic = (*_panic)(noescape(unsafe.Pointer(&p))) gp._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
atomic.Xadd(&runningPanicDefers, 1)
for { for {
d := gp._defer d := gp._defer
if d == nil { if d == nil {
...@@ -504,6 +506,8 @@ func gopanic(e interface{}) { ...@@ -504,6 +506,8 @@ func gopanic(e interface{}) {
sp := unsafe.Pointer(d.sp) // must be pointer so it gets adjusted during stack copy sp := unsafe.Pointer(d.sp) // must be pointer so it gets adjusted during stack copy
freedefer(d) freedefer(d)
if p.recovered { if p.recovered {
atomic.Xadd(&runningPanicDefers, -1)
gp._panic = p.link gp._panic = p.link
// Aborted panics are marked but remain on the g.panic list. // Aborted panics are marked but remain on the g.panic list.
// Remove them from the list. // Remove them from the list.
...@@ -527,6 +531,11 @@ func gopanic(e interface{}) { ...@@ -527,6 +531,11 @@ func gopanic(e interface{}) {
// and String methods to prepare the panic strings before startpanic. // and String methods to prepare the panic strings before startpanic.
preprintpanics(gp._panic) preprintpanics(gp._panic)
startpanic() startpanic()
// startpanic set panicking, which will block main from exiting,
// so now OK to decrement runningPanicDefers.
atomic.Xadd(&runningPanicDefers, -1)
printpanics(gp._panic) printpanics(gp._panic)
dopanic(0) // should not return dopanic(0) // should not return
*(*int)(nil) = 0 // not reached *(*int)(nil) = 0 // not reached
...@@ -597,7 +606,17 @@ func throw(s string) { ...@@ -597,7 +606,17 @@ func throw(s string) {
*(*int)(nil) = 0 // not reached *(*int)(nil) = 0 // not reached
} }
//uint32 runtime·panicking; // runningPanicDefers is non-zero while running deferred functions for panic.
// runningPanicDefers is incremented and decremented atomically.
// This is used to try hard to get a panic stack trace out when exiting.
var runningPanicDefers uint32
// panicking is non-zero when crashing the program for an unrecovered panic.
// panicking is incremented and decremented atomically.
var panicking uint32
// paniclk is held while printing the panic information and stack trace,
// so that two concurrent panics don't overlap their output.
var paniclk mutex var paniclk mutex
// Unwind the stack after a deferred function calls recover // Unwind the stack after a deferred function calls recover
......
...@@ -190,8 +190,17 @@ func main() { ...@@ -190,8 +190,17 @@ func main() {
// Make racy client program work: if panicking on // Make racy client program work: if panicking on
// another goroutine at the same time as main returns, // another goroutine at the same time as main returns,
// let the other goroutine finish printing the panic trace. // let the other goroutine finish printing the panic trace.
// Once it does, it will exit. See issue 3934. // Once it does, it will exit. See issues 3934 and 20018.
if panicking != 0 { if atomic.Load(&runningPanicDefers) != 0 {
// Running deferred functions should not take long.
for c := 0; c < 1000; c++ {
if atomic.Load(&runningPanicDefers) == 0 {
break
}
Gosched()
}
}
if atomic.Load(&panicking) != 0 {
gopark(nil, nil, "panicwait", traceEvGoStop, 1) gopark(nil, nil, "panicwait", traceEvGoStop, 1)
} }
......
...@@ -720,7 +720,6 @@ var ( ...@@ -720,7 +720,6 @@ var (
allm *m allm *m
allp [_MaxGomaxprocs + 1]*p allp [_MaxGomaxprocs + 1]*p
gomaxprocs int32 gomaxprocs int32
panicking uint32
ncpu int32 ncpu int32
forcegc forcegcstate forcegc forcegcstate
sched schedt sched schedt
......
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"runtime"
"sync"
)
func init() {
register("PanicRace", PanicRace)
}
func PanicRace() {
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer func() {
wg.Done()
runtime.Gosched()
}()
panic("crash")
}()
wg.Wait()
}
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