]> Cypherpunks.ru repositories - gostls13.git/commitdiff
misc, runtime, test: extra tests and benchmarks for defer
authorDan Scales <danscales@google.com>
Tue, 24 Sep 2019 00:46:38 +0000 (17:46 -0700)
committerDan Scales <danscales@google.com>
Wed, 25 Sep 2019 23:27:16 +0000 (23:27 +0000)
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: Keith Randall <khr@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
misc/cgo/test/cgo_test.go
misc/cgo/test/test.go
src/runtime/callers_test.go
src/runtime/crash_test.go
src/runtime/defer_test.go [new file with mode: 0644]
src/runtime/runtime_test.go
src/runtime/testdata/testprog/deadlock.go
test/codegen/stack.go

index c66df2cd46120772828cb709d801a80959d8387d..85f00a365331b6373a4edb4d08b5248dce4e32b9 100644 (file)
@@ -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) }
index 0a26bfb5cf26d9160d81af9638d74cf97df93c9c..0aa80ebc82c30cbe6903cfdee062c3e652eecab3 100644 (file)
@@ -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) {
index ad83f9969c4c97cd7fdae3ccc39eb72b0e665810..fcfd10deffe726d0ad267924aa1373ae95b87d3c 100644 (file)
@@ -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")
+       }
+}
index 89b55a8011d651e30e66cb8afd003c3406b0096b..7be52f499c3d586accde03d956c0bd0c5e506c25 100644 (file)
@@ -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)
        }
diff --git a/src/runtime/defer_test.go b/src/runtime/defer_test.go
new file mode 100644 (file)
index 0000000..0d3e8e9
--- /dev/null
@@ -0,0 +1,176 @@
+// 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")
+}
index 5ea9cbd88a125f01020f1a2950eac7c3ef40b666..37eacfea6469e0954c6d6d6baa0199a8fac10542 100644 (file)
@@ -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)
index 5f0d1200047e0c34fc6a2cf65bbe351d199f9500..9ca0fc344fc2c8ff60a76b3c5fb26f2fc7ddb69c 100644 (file)
@@ -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() {
index 37d378aa7877373e350d7512272166a17feddc48..7d70024cdd051124c81d33bf06f788c3e9144881 100644 (file)
@@ -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() {}()
 }