Commit 225f484c authored by Dan Scales's avatar Dan Scales

misc, runtime, test: extra tests and benchmarks for defer

Add a bunch of extra tests and benchmarks for defer, in preparation for new
low-cost (open-coded) implementation of defers (see #34481),

 - New file defer_test.go that tests a bunch more unusual defer scenarios,
   including things that might have problems for open-coded defers.
 - Additions to callers_test.go actually verifying what the stack trace looks like
   for various panic or panic-recover scenarios.
 - Additions to crash_test.go testing several more crash scenarios involving
   recursive panics.
 - New benchmark in runtime_test.go measuring speed of panic-recover
 - New CGo benchmark in cgo_test.go calling from Go to C back to Go that
   shows defer overhead

Updates #34481

Change-Id: I423523f3e05fc0229d4277dd00073289a5526188
Reviewed-on: https://go-review.googlesource.com/c/go/+/197017
Run-TryBot: Dan Scales <danscales@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarKeith Randall <khr@golang.org>
Reviewed-by: default avatarAustin Clements <austin@google.com>
parent 8c99e45e
......@@ -91,5 +91,6 @@ func TestThreadLock(t *testing.T) { testThreadLockFunc(t) }
func TestUnsignedInt(t *testing.T) { testUnsignedInt(t) }
func TestZeroArgCallback(t *testing.T) { testZeroArgCallback(t) }
func BenchmarkCgoCall(b *testing.B) { benchCgoCall(b) }
func BenchmarkGoString(b *testing.B) { benchGoString(b) }
func BenchmarkCgoCall(b *testing.B) { benchCgoCall(b) }
func BenchmarkGoString(b *testing.B) { benchGoString(b) }
func BenchmarkCGoCallback(b *testing.B) { benchCallback(b) }
......@@ -1000,6 +1000,17 @@ func benchCgoCall(b *testing.B) {
}
}
// Benchmark measuring overhead from Go to C and back to Go (via a callback)
func benchCallback(b *testing.B) {
var x = false
for i := 0; i < b.N; i++ {
nestedCall(func() { x = true })
}
if !x {
b.Fatal("nestedCall was not invoked")
}
}
var sinkString string
func benchGoString(b *testing.B) {
......
......@@ -5,25 +5,26 @@
package runtime_test
import (
"reflect"
"runtime"
"strings"
"testing"
)
func f1(pan bool) []uintptr {
return f2(pan) // line 14
return f2(pan) // line 15
}
func f2(pan bool) []uintptr {
return f3(pan) // line 18
return f3(pan) // line 19
}
func f3(pan bool) []uintptr {
if pan {
panic("f3") // line 23
panic("f3") // line 24
}
ret := make([]uintptr, 20)
return ret[:runtime.Callers(0, ret)] // line 26
return ret[:runtime.Callers(0, ret)] // line 27
}
func testCallers(t *testing.T, pcs []uintptr, pan bool) {
......@@ -47,16 +48,16 @@ func testCallers(t *testing.T, pcs []uintptr, pan bool) {
var f3Line int
if pan {
f3Line = 23
f3Line = 24
} else {
f3Line = 26
f3Line = 27
}
want := []struct {
name string
line int
}{
{"f1", 14},
{"f2", 18},
{"f1", 15},
{"f2", 19},
{"f3", f3Line},
}
for _, w := range want {
......@@ -66,11 +67,33 @@ func testCallers(t *testing.T, pcs []uintptr, pan bool) {
}
}
func testCallersEqual(t *testing.T, pcs []uintptr, want []string) {
got := make([]string, 0, len(want))
frames := runtime.CallersFrames(pcs)
for {
frame, more := frames.Next()
if !more || len(got) >= len(want) {
break
}
got = append(got, frame.Function)
}
if !reflect.DeepEqual(want, got) {
t.Fatalf("wanted %v, got %v", want, got)
}
}
func TestCallers(t *testing.T) {
testCallers(t, f1(false), false)
}
func TestCallersPanic(t *testing.T) {
// Make sure we don't have any extra frames on the stack (due to
// open-coded defer processing)
want := []string{"runtime.Callers", "runtime_test.TestCallersPanic.func1",
"runtime.gopanic", "runtime_test.f3", "runtime_test.f2", "runtime_test.f1",
"runtime_test.TestCallersPanic"}
defer func() {
if r := recover(); r == nil {
t.Fatal("did not panic")
......@@ -78,6 +101,90 @@ func TestCallersPanic(t *testing.T) {
pcs := make([]uintptr, 20)
pcs = pcs[:runtime.Callers(0, pcs)]
testCallers(t, pcs, true)
testCallersEqual(t, pcs, want)
}()
f1(true)
}
func TestCallersDoublePanic(t *testing.T) {
// Make sure we don't have any extra frames on the stack (due to
// open-coded defer processing)
want := []string{"runtime.Callers", "runtime_test.TestCallersDoublePanic.func1.1",
"runtime.gopanic", "runtime_test.TestCallersDoublePanic.func1", "runtime.gopanic", "runtime_test.TestCallersDoublePanic"}
defer func() {
defer func() {
pcs := make([]uintptr, 20)
pcs = pcs[:runtime.Callers(0, pcs)]
if recover() == nil {
t.Fatal("did not panic")
}
testCallersEqual(t, pcs, want)
}()
if recover() == nil {
t.Fatal("did not panic")
}
panic(2)
}()
panic(1)
}
// Test that a defer after a successful recovery looks like it is called directly
// from the function with the defers.
func TestCallersAfterRecovery(t *testing.T) {
want := []string{"runtime.Callers", "runtime_test.TestCallersAfterRecovery.func1", "runtime_test.TestCallersAfterRecovery"}
defer func() {
pcs := make([]uintptr, 20)
pcs = pcs[:runtime.Callers(0, pcs)]
testCallersEqual(t, pcs, want)
}()
defer func() {
if recover() == nil {
t.Fatal("did not recover from panic")
}
}()
panic(1)
}
func TestCallersNilPointerPanic(t *testing.T) {
// Make sure we don't have any extra frames on the stack (due to
// open-coded defer processing)
want := []string{"runtime.Callers", "runtime_test.TestCallersNilPointerPanic.func1",
"runtime.gopanic", "runtime.panicmem", "runtime.sigpanic",
"runtime_test.TestCallersNilPointerPanic"}
defer func() {
if r := recover(); r == nil {
t.Fatal("did not panic")
}
pcs := make([]uintptr, 20)
pcs = pcs[:runtime.Callers(0, pcs)]
testCallersEqual(t, pcs, want)
}()
var p *int
if *p == 3 {
t.Fatal("did not see nil pointer panic")
}
}
func TestCallersDivZeroPanic(t *testing.T) {
// Make sure we don't have any extra frames on the stack (due to
// open-coded defer processing)
want := []string{"runtime.Callers", "runtime_test.TestCallersDivZeroPanic.func1",
"runtime.gopanic", "runtime.panicdivide",
"runtime_test.TestCallersDivZeroPanic"}
defer func() {
if r := recover(); r == nil {
t.Fatal("did not panic")
}
pcs := make([]uintptr, 20)
pcs = pcs[:runtime.Callers(0, pcs)]
testCallersEqual(t, pcs, want)
}()
var n int
if 5/n == 1 {
t.Fatal("did not see divide-by-sizer panic")
}
}
......@@ -260,6 +260,30 @@ panic: again
}
func TestRecursivePanic2(t *testing.T) {
output := runTestProg(t, "testprog", "RecursivePanic2")
want := `first panic
second panic
panic: third panic
`
if !strings.HasPrefix(output, want) {
t.Fatalf("output does not start with %q:\n%s", want, output)
}
}
func TestRecursivePanic3(t *testing.T) {
output := runTestProg(t, "testprog", "RecursivePanic3")
want := `panic: first 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!"
......@@ -422,7 +446,7 @@ func TestNetpollDeadlock(t *testing.T) {
func TestPanicTraceback(t *testing.T) {
t.Parallel()
output := runTestProg(t, "testprog", "PanicTraceback")
want := "panic: hello"
want := "panic: hello\n\tpanic: panic pt2\n\tpanic: panic pt1\n"
if !strings.HasPrefix(output, want) {
t.Fatalf("output does not start with %q:\n%s", want, output)
}
......
// Copyright 2019 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 runtime_test
import (
"fmt"
"reflect"
"runtime"
"testing"
)
// Make sure open-coded defer exit code is not lost, even when there is an
// unconditional panic (hence no return from the function)
func TestUnconditionalPanic(t *testing.T) {
defer func() {
if recover() == nil {
t.Fatal("expected unconditional panic")
}
}()
panic("panic should be recovered")
}
var glob int = 3
// Test an open-coded defer and non-open-coded defer - make sure both defers run
// and call recover()
func TestOpenAndNonOpenDefers(t *testing.T) {
for {
// Non-open defer because in a loop
defer func(n int) {
if recover() == nil {
t.Fatal("expected testNonOpen panic")
}
}(3)
if glob > 2 {
break
}
}
testOpen(t, 47)
panic("testNonOpenDefer")
}
//go:noinline
func testOpen(t *testing.T, arg int) {
defer func(n int) {
if recover() == nil {
t.Fatal("expected testOpen panic")
}
}(4)
if arg > 2 {
panic("testOpenDefer")
}
}
// Test a non-open-coded defer and an open-coded defer - make sure both defers run
// and call recover()
func TestNonOpenAndOpenDefers(t *testing.T) {
testOpen(t, 47)
for {
// Non-open defer because in a loop
defer func(n int) {
if recover() == nil {
t.Fatal("expected testNonOpen panic")
}
}(3)
if glob > 2 {
break
}
}
panic("testNonOpenDefer")
}
var list []int
// Make sure that conditional open-coded defers are activated correctly and run in
// the correct order.
func TestConditionalDefers(t *testing.T) {
list = make([]int, 0, 10)
defer func() {
if recover() == nil {
t.Fatal("expected panic")
}
want := []int{4, 2, 1}
if !reflect.DeepEqual(want, list) {
t.Fatal(fmt.Sprintf("wanted %v, got %v", want, list))
}
}()
testConditionalDefers(8)
}
func testConditionalDefers(n int) {
doappend := func(i int) {
list = append(list, i)
}
defer doappend(1)
if n > 5 {
defer doappend(2)
if n > 8 {
defer doappend(3)
} else {
defer doappend(4)
}
}
panic("test")
}
// Test that there is no compile-time or run-time error if an open-coded defer
// call is removed by constant propagation and dead-code elimination.
func TestDisappearingDefer(t *testing.T) {
switch runtime.GOOS {
case "invalidOS":
defer func() {
t.Fatal("Defer shouldn't run")
}()
}
}
// This tests an extra recursive panic behavior that is only specified in the
// code. Suppose a first panic P1 happens and starts processing defer calls. If
// a second panic P2 happens while processing defer call D in frame F, then defer
// call processing is restarted (with some potentially new defer calls created by
// D or its callees). If the defer processing reaches the started defer call D
// again in the defer stack, then the original panic P1 is aborted and cannot
// continue panic processing or be recovered. If the panic P2 does a recover at
// some point, it will naturally the original panic P1 from the stack, since the
// original panic had to be in frame F or a descendant of F.
func TestAbortedPanic(t *testing.T) {
defer func() {
// The first panic should have been "aborted", so there is
// no other panic to recover
r := recover()
if r != nil {
t.Fatal(fmt.Sprintf("wanted nil recover, got %v", r))
}
}()
defer func() {
r := recover()
if r != "panic2" {
t.Fatal(fmt.Sprintf("wanted %v, got %v", "panic2", r))
}
}()
defer func() {
panic("panic2")
}()
panic("panic1")
}
// This tests that recover() does not succeed unless it is called directly from a
// defer function that is directly called by the panic. Here, we first call it
// from a defer function that is created by the defer function called directly by
// the panic. In
func TestRecoverMatching(t *testing.T) {
defer func() {
r := recover()
if r != "panic1" {
t.Fatal(fmt.Sprintf("wanted %v, got %v", "panic1", r))
}
}()
defer func() {
defer func() {
// Shouldn't succeed, even though it is called directly
// from a defer function, since this defer function was
// not directly called by the panic.
r := recover()
if r != nil {
t.Fatal(fmt.Sprintf("wanted nil recover, got %v", r))
}
}()
}()
panic("panic1")
}
......@@ -122,6 +122,21 @@ func BenchmarkDeferMany(b *testing.B) {
}
}
func BenchmarkPanicRecover(b *testing.B) {
for i := 0; i < b.N; i++ {
defer3()
}
}
func defer3() {
defer func(x, y, z int) {
if recover() == nil {
panic("failed recover")
}
}(1, 2, 3)
panic("hi")
}
// golang.org/issue/7063
func TestStopCPUProfilingWithProfilerOff(t *testing.T) {
SetCPUProfileRate(0)
......
......@@ -22,6 +22,8 @@ func init() {
register("StackOverflow", StackOverflow)
register("ThreadExhaustion", ThreadExhaustion)
register("RecursivePanic", RecursivePanic)
register("RecursivePanic2", RecursivePanic2)
register("RecursivePanic3", RecursivePanic3)
register("GoexitExit", GoexitExit)
register("GoNil", GoNil)
register("MainGoroutineID", MainGoroutineID)
......@@ -111,6 +113,39 @@ func RecursivePanic() {
panic("again")
}
// Same as RecursivePanic, but do the first recover and the second panic in
// separate defers, and make sure they are executed in the correct order.
func RecursivePanic2() {
func() {
defer func() {
fmt.Println(recover())
}()
var x [8192]byte
func(x [8192]byte) {
defer func() {
panic("second panic")
}()
defer func() {
fmt.Println(recover())
}()
panic("first panic")
}(x)
}()
panic("third panic")
}
// Make sure that the first panic finished as a panic, even though the second
// panic was recovered
func RecursivePanic3() {
defer func() {
defer func() {
recover()
}()
panic("second panic")
}()
panic("first panic")
}
func GoexitExit() {
println("t1")
go func() {
......
......@@ -110,7 +110,11 @@ func MightPanic(a []int, i, j, k, s int) {
_ = i / j // panicDivide
}
// Put a defer in a loop, so second defer is not open-coded
func Defer() {
for i := 0; i < 2; i++ {
defer func() {}()
}
// amd64:`CALL\truntime\.deferprocStack`
defer func() {}()
}
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