mirror of
https://github.com/golang/go
synced 2024-11-20 06:44:40 -07:00
59495e8dfd
Fixes #9791 g.issystem flag setup races with other code wherever we set it. Even if we set both in parent goroutine and in the system goroutine, it is still possible that some other goroutine crashes before the flag is set. We could pass issystem flag to newproc1, but we start all goroutines with go nowadays. Instead look at g.startpc to distinguish system goroutines (similar to topofstack). Change-Id: Ia3467968dee27fa07d9fecedd4c2b00928f26645 Reviewed-on: https://go-review.googlesource.com/4113 Reviewed-by: Keith Randall <khr@golang.org>
585 lines
11 KiB
Go
585 lines
11 KiB
Go
// Copyright 2012 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"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"regexp"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"text/template"
|
|
)
|
|
|
|
func testEnv(cmd *exec.Cmd) *exec.Cmd {
|
|
if cmd.Env != nil {
|
|
panic("environment already set")
|
|
}
|
|
for _, env := range os.Environ() {
|
|
// Exclude GODEBUG from the environment to prevent its output
|
|
// from breaking tests that are trying to parse other command output.
|
|
if strings.HasPrefix(env, "GODEBUG=") {
|
|
continue
|
|
}
|
|
// Exclude GOTRACEBACK for the same reason.
|
|
if strings.HasPrefix(env, "GOTRACEBACK=") {
|
|
continue
|
|
}
|
|
cmd.Env = append(cmd.Env, env)
|
|
}
|
|
return cmd
|
|
}
|
|
|
|
func executeTest(t *testing.T, templ string, data interface{}, extra ...string) string {
|
|
switch runtime.GOOS {
|
|
case "android", "nacl":
|
|
t.Skipf("skipping on %s", runtime.GOOS)
|
|
}
|
|
|
|
checkStaleRuntime(t)
|
|
|
|
st := template.Must(template.New("crashSource").Parse(templ))
|
|
|
|
dir, err := ioutil.TempDir("", "go-build")
|
|
if err != nil {
|
|
t.Fatalf("failed to create temp directory: %v", err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
|
|
src := filepath.Join(dir, "main.go")
|
|
f, err := os.Create(src)
|
|
if err != nil {
|
|
t.Fatalf("failed to create file: %v", err)
|
|
}
|
|
err = st.Execute(f, data)
|
|
if err != nil {
|
|
f.Close()
|
|
t.Fatalf("failed to execute template: %v", err)
|
|
}
|
|
if err := f.Close(); err != nil {
|
|
t.Fatalf("failed to close file: %v", err)
|
|
}
|
|
|
|
for i := 0; i < len(extra); i += 2 {
|
|
if err := ioutil.WriteFile(filepath.Join(dir, extra[i]), []byte(extra[i+1]), 0666); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
cmd := exec.Command("go", "build", "-o", "a.exe")
|
|
cmd.Dir = dir
|
|
out, err := testEnv(cmd).CombinedOutput()
|
|
if err != nil {
|
|
t.Fatalf("building source: %v\n%s", err, out)
|
|
}
|
|
|
|
got, _ := testEnv(exec.Command(filepath.Join(dir, "a.exe"))).CombinedOutput()
|
|
return string(got)
|
|
}
|
|
|
|
var (
|
|
staleRuntimeOnce sync.Once // guards init of staleRuntimeErr
|
|
staleRuntimeErr error
|
|
)
|
|
|
|
func checkStaleRuntime(t *testing.T) {
|
|
staleRuntimeOnce.Do(func() {
|
|
// 'go run' uses the installed copy of runtime.a, which may be out of date.
|
|
out, err := testEnv(exec.Command("go", "list", "-f", "{{.Stale}}", "runtime")).CombinedOutput()
|
|
if err != nil {
|
|
staleRuntimeErr = fmt.Errorf("failed to execute 'go list': %v\n%v", err, string(out))
|
|
return
|
|
}
|
|
if string(out) != "false\n" {
|
|
staleRuntimeErr = fmt.Errorf("Stale runtime.a. Run 'go install runtime'.")
|
|
}
|
|
})
|
|
if staleRuntimeErr != nil {
|
|
t.Fatal(staleRuntimeErr)
|
|
}
|
|
}
|
|
|
|
func testCrashHandler(t *testing.T, cgo bool) {
|
|
type crashTest struct {
|
|
Cgo bool
|
|
}
|
|
output := executeTest(t, crashSource, &crashTest{Cgo: cgo})
|
|
want := "main: recovered done\nnew-thread: recovered done\nsecond-new-thread: recovered done\nmain-again: recovered done\n"
|
|
if output != want {
|
|
t.Fatalf("output:\n%s\n\nwanted:\n%s", output, want)
|
|
}
|
|
}
|
|
|
|
func TestCrashHandler(t *testing.T) {
|
|
testCrashHandler(t, false)
|
|
}
|
|
|
|
func testDeadlock(t *testing.T, source string) {
|
|
output := executeTest(t, source, nil)
|
|
want := "fatal error: all goroutines are asleep - deadlock!\n"
|
|
if !strings.HasPrefix(output, want) {
|
|
t.Fatalf("output does not start with %q:\n%s", want, output)
|
|
}
|
|
}
|
|
|
|
func TestSimpleDeadlock(t *testing.T) {
|
|
testDeadlock(t, simpleDeadlockSource)
|
|
}
|
|
|
|
func TestInitDeadlock(t *testing.T) {
|
|
testDeadlock(t, initDeadlockSource)
|
|
}
|
|
|
|
func TestLockedDeadlock(t *testing.T) {
|
|
testDeadlock(t, lockedDeadlockSource)
|
|
}
|
|
|
|
func TestLockedDeadlock2(t *testing.T) {
|
|
testDeadlock(t, lockedDeadlockSource2)
|
|
}
|
|
|
|
func TestGoexitDeadlock(t *testing.T) {
|
|
output := executeTest(t, goexitDeadlockSource, nil)
|
|
want := "no goroutines (main called runtime.Goexit) - deadlock!"
|
|
if !strings.Contains(output, want) {
|
|
t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
|
|
}
|
|
}
|
|
|
|
func TestStackOverflow(t *testing.T) {
|
|
output := executeTest(t, stackOverflowSource, nil)
|
|
want := "runtime: goroutine stack exceeds 4194304-byte limit\nfatal error: stack overflow"
|
|
if !strings.HasPrefix(output, want) {
|
|
t.Fatalf("output does not start with %q:\n%s", want, output)
|
|
}
|
|
}
|
|
|
|
func TestThreadExhaustion(t *testing.T) {
|
|
output := executeTest(t, threadExhaustionSource, nil)
|
|
want := "runtime: program exceeds 10-thread limit\nfatal error: thread exhaustion"
|
|
if !strings.HasPrefix(output, want) {
|
|
t.Fatalf("output does not start with %q:\n%s", want, output)
|
|
}
|
|
}
|
|
|
|
func TestRecursivePanic(t *testing.T) {
|
|
output := executeTest(t, recursivePanicSource, nil)
|
|
want := `wrap: bad
|
|
panic: again
|
|
|
|
`
|
|
if !strings.HasPrefix(output, want) {
|
|
t.Fatalf("output does not start with %q:\n%s", want, output)
|
|
}
|
|
|
|
}
|
|
|
|
func TestGoexitCrash(t *testing.T) {
|
|
output := executeTest(t, goexitExitSource, nil)
|
|
want := "no goroutines (main called runtime.Goexit) - deadlock!"
|
|
if !strings.Contains(output, want) {
|
|
t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
|
|
}
|
|
}
|
|
|
|
func TestGoexitDefer(t *testing.T) {
|
|
c := make(chan struct{})
|
|
go func() {
|
|
defer func() {
|
|
r := recover()
|
|
if r != nil {
|
|
t.Errorf("non-nil recover during Goexit")
|
|
}
|
|
c <- struct{}{}
|
|
}()
|
|
runtime.Goexit()
|
|
}()
|
|
// Note: if the defer fails to run, we will get a deadlock here
|
|
<-c
|
|
}
|
|
|
|
func TestGoNil(t *testing.T) {
|
|
output := executeTest(t, goNilSource, nil)
|
|
want := "go of nil func value"
|
|
if !strings.Contains(output, want) {
|
|
t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
|
|
}
|
|
}
|
|
|
|
func TestMainGoroutineId(t *testing.T) {
|
|
output := executeTest(t, mainGoroutineIdSource, nil)
|
|
want := "panic: test\n\ngoroutine 1 [running]:\n"
|
|
if !strings.HasPrefix(output, want) {
|
|
t.Fatalf("output does not start with %q:\n%s", want, output)
|
|
}
|
|
}
|
|
|
|
func TestNoHelperGoroutines(t *testing.T) {
|
|
output := executeTest(t, noHelperGoroutinesSource, nil)
|
|
matches := regexp.MustCompile(`goroutine [0-9]+ \[`).FindAllStringSubmatch(output, -1)
|
|
if len(matches) != 1 || matches[0][0] != "goroutine 1 [" {
|
|
t.Fatalf("want to see only goroutine 1, see:\n%s", output)
|
|
}
|
|
}
|
|
|
|
func TestBreakpoint(t *testing.T) {
|
|
output := executeTest(t, breakpointSource, nil)
|
|
want := "runtime.Breakpoint()"
|
|
if !strings.Contains(output, want) {
|
|
t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
|
|
}
|
|
}
|
|
|
|
const crashSource = `
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"runtime"
|
|
)
|
|
|
|
{{if .Cgo}}
|
|
import "C"
|
|
{{end}}
|
|
|
|
func test(name string) {
|
|
defer func() {
|
|
if x := recover(); x != nil {
|
|
fmt.Printf(" recovered")
|
|
}
|
|
fmt.Printf(" done\n")
|
|
}()
|
|
fmt.Printf("%s:", name)
|
|
var s *string
|
|
_ = *s
|
|
fmt.Print("SHOULD NOT BE HERE")
|
|
}
|
|
|
|
func testInNewThread(name string) {
|
|
c := make(chan bool)
|
|
go func() {
|
|
runtime.LockOSThread()
|
|
test(name)
|
|
c <- true
|
|
}()
|
|
<-c
|
|
}
|
|
|
|
func main() {
|
|
runtime.LockOSThread()
|
|
test("main")
|
|
testInNewThread("new-thread")
|
|
testInNewThread("second-new-thread")
|
|
test("main-again")
|
|
}
|
|
`
|
|
|
|
const simpleDeadlockSource = `
|
|
package main
|
|
func main() {
|
|
select {}
|
|
}
|
|
`
|
|
|
|
const initDeadlockSource = `
|
|
package main
|
|
func init() {
|
|
select {}
|
|
}
|
|
func main() {
|
|
}
|
|
`
|
|
|
|
const lockedDeadlockSource = `
|
|
package main
|
|
import "runtime"
|
|
func main() {
|
|
runtime.LockOSThread()
|
|
select {}
|
|
}
|
|
`
|
|
|
|
const lockedDeadlockSource2 = `
|
|
package main
|
|
import (
|
|
"runtime"
|
|
"time"
|
|
)
|
|
func main() {
|
|
go func() {
|
|
runtime.LockOSThread()
|
|
select {}
|
|
}()
|
|
time.Sleep(time.Millisecond)
|
|
select {}
|
|
}
|
|
`
|
|
|
|
const goexitDeadlockSource = `
|
|
package main
|
|
import (
|
|
"runtime"
|
|
)
|
|
|
|
func F() {
|
|
for i := 0; i < 10; i++ {
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
go F()
|
|
go F()
|
|
runtime.Goexit()
|
|
}
|
|
`
|
|
|
|
const stackOverflowSource = `
|
|
package main
|
|
|
|
import "runtime/debug"
|
|
|
|
func main() {
|
|
debug.SetMaxStack(4<<20)
|
|
f(make([]byte, 10))
|
|
}
|
|
|
|
func f(x []byte) byte {
|
|
var buf [64<<10]byte
|
|
return x[0] + f(buf[:])
|
|
}
|
|
`
|
|
|
|
const threadExhaustionSource = `
|
|
package main
|
|
|
|
import (
|
|
"runtime"
|
|
"runtime/debug"
|
|
)
|
|
|
|
func main() {
|
|
debug.SetMaxThreads(10)
|
|
c := make(chan int)
|
|
for i := 0; i < 100; i++ {
|
|
go func() {
|
|
runtime.LockOSThread()
|
|
c <- 0
|
|
select{}
|
|
}()
|
|
<-c
|
|
}
|
|
}
|
|
`
|
|
|
|
const recursivePanicSource = `
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
)
|
|
|
|
func main() {
|
|
func() {
|
|
defer func() {
|
|
fmt.Println(recover())
|
|
}()
|
|
var x [8192]byte
|
|
func(x [8192]byte) {
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
panic("wrap: " + err.(string))
|
|
}
|
|
}()
|
|
panic("bad")
|
|
}(x)
|
|
}()
|
|
panic("again")
|
|
}
|
|
`
|
|
|
|
const goexitExitSource = `
|
|
package main
|
|
|
|
import (
|
|
"runtime"
|
|
"time"
|
|
)
|
|
|
|
func main() {
|
|
go func() {
|
|
time.Sleep(time.Millisecond)
|
|
}()
|
|
i := 0
|
|
runtime.SetFinalizer(&i, func(p *int) {})
|
|
runtime.GC()
|
|
runtime.Goexit()
|
|
}
|
|
`
|
|
|
|
const goNilSource = `
|
|
package main
|
|
|
|
func main() {
|
|
defer func() {
|
|
recover()
|
|
}()
|
|
var f func()
|
|
go f()
|
|
select{}
|
|
}
|
|
`
|
|
|
|
const mainGoroutineIdSource = `
|
|
package main
|
|
func main() {
|
|
panic("test")
|
|
}
|
|
`
|
|
|
|
const noHelperGoroutinesSource = `
|
|
package main
|
|
import (
|
|
"runtime"
|
|
"time"
|
|
)
|
|
func init() {
|
|
i := 0
|
|
runtime.SetFinalizer(&i, func(p *int) {})
|
|
time.AfterFunc(time.Hour, func() {})
|
|
panic("oops")
|
|
}
|
|
func main() {
|
|
}
|
|
`
|
|
|
|
const breakpointSource = `
|
|
package main
|
|
import "runtime"
|
|
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()
|
|
}
|
|
|
|
func TestNetpollDeadlock(t *testing.T) {
|
|
output := executeTest(t, netpollDeadlockSource, nil)
|
|
want := "done\n"
|
|
if !strings.HasSuffix(output, want) {
|
|
t.Fatalf("output does not start with %q:\n%s", want, output)
|
|
}
|
|
}
|
|
|
|
const netpollDeadlockSource = `
|
|
package main
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
)
|
|
func init() {
|
|
fmt.Println("dialing")
|
|
c, err := net.Dial("tcp", "localhost:14356")
|
|
if err == nil {
|
|
c.Close()
|
|
} else {
|
|
fmt.Println("error: ", err)
|
|
}
|
|
}
|
|
func main() {
|
|
fmt.Println("done")
|
|
}
|
|
`
|