2012-05-29 23:10:54 -06:00
|
|
|
// 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 (
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
2014-05-20 10:10:19 -06:00
|
|
|
"runtime"
|
2013-02-20 01:15:02 -07:00
|
|
|
"strings"
|
2012-05-29 23:10:54 -06:00
|
|
|
"testing"
|
|
|
|
"text/template"
|
|
|
|
)
|
|
|
|
|
2013-06-28 08:37:06 -06:00
|
|
|
// testEnv excludes GODEBUG from the environment
|
2013-04-07 12:37:37 -06:00
|
|
|
// to prevent its output from breaking tests that
|
|
|
|
// are trying to parse other command output.
|
|
|
|
func testEnv(cmd *exec.Cmd) *exec.Cmd {
|
|
|
|
if cmd.Env != nil {
|
|
|
|
panic("environment already set")
|
|
|
|
}
|
|
|
|
for _, env := range os.Environ() {
|
2013-06-28 08:37:06 -06:00
|
|
|
if strings.HasPrefix(env, "GODEBUG=") {
|
2013-04-07 12:37:37 -06:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
cmd.Env = append(cmd.Env, env)
|
|
|
|
}
|
|
|
|
return cmd
|
|
|
|
}
|
|
|
|
|
2013-02-20 01:15:02 -07:00
|
|
|
func executeTest(t *testing.T, templ string, data interface{}) string {
|
2014-07-08 12:47:52 -06:00
|
|
|
switch runtime.GOOS {
|
|
|
|
case "android", "nacl":
|
|
|
|
t.Skipf("skipping on %s", runtime.GOOS)
|
2014-05-20 10:10:19 -06:00
|
|
|
}
|
|
|
|
|
2013-02-20 01:15:02 -07:00
|
|
|
checkStaleRuntime(t)
|
2012-05-29 23:10:54 -06:00
|
|
|
|
2013-02-20 01:15:02 -07:00
|
|
|
st := template.Must(template.New("crashSource").Parse(templ))
|
2012-05-29 23:10:54 -06:00
|
|
|
|
|
|
|
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 {
|
2013-08-12 12:04:10 -06:00
|
|
|
t.Fatalf("failed to create file: %v", err)
|
2012-05-29 23:10:54 -06:00
|
|
|
}
|
2013-02-20 01:15:02 -07:00
|
|
|
err = st.Execute(f, data)
|
2012-05-29 23:10:54 -06:00
|
|
|
if err != nil {
|
|
|
|
f.Close()
|
|
|
|
t.Fatalf("failed to execute template: %v", err)
|
|
|
|
}
|
2013-08-12 12:04:10 -06:00
|
|
|
if err := f.Close(); err != nil {
|
|
|
|
t.Fatalf("failed to close file: %v", err)
|
|
|
|
}
|
2012-05-29 23:10:54 -06:00
|
|
|
|
2013-04-07 12:37:37 -06:00
|
|
|
got, _ := testEnv(exec.Command("go", "run", src)).CombinedOutput()
|
2013-02-20 01:15:02 -07:00
|
|
|
return string(got)
|
|
|
|
}
|
|
|
|
|
|
|
|
func checkStaleRuntime(t *testing.T) {
|
|
|
|
// 'go run' uses the installed copy of runtime.a, which may be out of date.
|
2013-04-07 12:37:37 -06:00
|
|
|
out, err := testEnv(exec.Command("go", "list", "-f", "{{.Stale}}", "runtime")).CombinedOutput()
|
2012-05-29 23:10:54 -06:00
|
|
|
if err != nil {
|
2013-02-20 01:15:02 -07:00
|
|
|
t.Fatalf("failed to execute 'go list': %v\n%v", err, string(out))
|
|
|
|
}
|
|
|
|
if string(out) != "false\n" {
|
|
|
|
t.Fatalf("Stale runtime.a. Run 'go install runtime'.")
|
2012-05-29 23:10:54 -06:00
|
|
|
}
|
2013-02-20 01:15:02 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func testCrashHandler(t *testing.T, cgo bool) {
|
|
|
|
type crashTest struct {
|
|
|
|
Cgo bool
|
|
|
|
}
|
2013-08-15 20:34:06 -06:00
|
|
|
output := executeTest(t, crashSource, &crashTest{Cgo: cgo})
|
2012-05-29 23:10:54 -06:00
|
|
|
want := "main: recovered done\nnew-thread: recovered done\nsecond-new-thread: recovered done\nmain-again: recovered done\n"
|
2013-08-15 20:34:06 -06:00
|
|
|
if output != want {
|
|
|
|
t.Fatalf("output:\n%s\n\nwanted:\n%s", output, want)
|
2012-05-29 23:10:54 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCrashHandler(t *testing.T) {
|
2013-02-20 01:15:02 -07:00
|
|
|
testCrashHandler(t, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testDeadlock(t *testing.T, source string) {
|
2013-08-15 20:34:06 -06:00
|
|
|
output := executeTest(t, source, nil)
|
2013-02-20 01:15:02 -07:00
|
|
|
want := "fatal error: all goroutines are asleep - deadlock!\n"
|
2013-08-15 20:34:06 -06:00
|
|
|
if !strings.HasPrefix(output, want) {
|
|
|
|
t.Fatalf("output does not start with %q:\n%s", want, output)
|
2013-02-20 01:15:02 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
2012-05-29 23:10:54 -06:00
|
|
|
}
|
|
|
|
|
2013-03-05 00:40:17 -07:00
|
|
|
func TestGoexitDeadlock(t *testing.T) {
|
2013-08-15 20:34:06 -06:00
|
|
|
output := executeTest(t, goexitDeadlockSource, nil)
|
runtime: crash when func main calls Goexit and all other goroutines exit
This has typically crashed in the past, although usually with
an 'all goroutines are asleep - deadlock!' message that shows
no goroutines (because there aren't any).
Previous discussion at:
https://groups.google.com/d/msg/golang-nuts/uCT_7WxxopQ/BoSBlLFzUTkJ
https://groups.google.com/d/msg/golang-dev/KUojayEr20I/u4fp_Ej5PdUJ
http://golang.org/issue/7711
There is general agreement that runtime.Goexit terminates the
main goroutine, so that main cannot return, so the program does
not exit.
The interpretation that all other goroutines exiting causes an
exit(0) is relatively new and was not part of those discussions.
That is what this CL changes.
Thankfully, even though the exit(0) has been there for a while,
some other accounting bugs made it very difficult to trigger,
so it is reasonable to replace. In particular, see golang.org/issue/7711#c10
for an examination of the behavior across past releases.
Fixes #7711.
LGTM=iant, r
R=golang-codereviews, iant, dvyukov, r
CC=golang-codereviews
https://golang.org/cl/88210044
2014-04-16 11:12:18 -06:00
|
|
|
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)
|
2013-08-15 20:34:06 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
2013-03-05 00:40:17 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-16 20:25:26 -06:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-03-07 09:50:30 -07:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
runtime: crash when func main calls Goexit and all other goroutines exit
This has typically crashed in the past, although usually with
an 'all goroutines are asleep - deadlock!' message that shows
no goroutines (because there aren't any).
Previous discussion at:
https://groups.google.com/d/msg/golang-nuts/uCT_7WxxopQ/BoSBlLFzUTkJ
https://groups.google.com/d/msg/golang-dev/KUojayEr20I/u4fp_Ej5PdUJ
http://golang.org/issue/7711
There is general agreement that runtime.Goexit terminates the
main goroutine, so that main cannot return, so the program does
not exit.
The interpretation that all other goroutines exiting causes an
exit(0) is relatively new and was not part of those discussions.
That is what this CL changes.
Thankfully, even though the exit(0) has been there for a while,
some other accounting bugs made it very difficult to trigger,
so it is reasonable to replace. In particular, see golang.org/issue/7711#c10
for an examination of the behavior across past releases.
Fixes #7711.
LGTM=iant, r
R=golang-codereviews, iant, dvyukov, r
CC=golang-codereviews
https://golang.org/cl/88210044
2014-04-16 11:12:18 -06:00
|
|
|
func TestGoexitCrash(t *testing.T) {
|
2014-04-15 09:48:17 -06:00
|
|
|
output := executeTest(t, goexitExitSource, nil)
|
runtime: crash when func main calls Goexit and all other goroutines exit
This has typically crashed in the past, although usually with
an 'all goroutines are asleep - deadlock!' message that shows
no goroutines (because there aren't any).
Previous discussion at:
https://groups.google.com/d/msg/golang-nuts/uCT_7WxxopQ/BoSBlLFzUTkJ
https://groups.google.com/d/msg/golang-dev/KUojayEr20I/u4fp_Ej5PdUJ
http://golang.org/issue/7711
There is general agreement that runtime.Goexit terminates the
main goroutine, so that main cannot return, so the program does
not exit.
The interpretation that all other goroutines exiting causes an
exit(0) is relatively new and was not part of those discussions.
That is what this CL changes.
Thankfully, even though the exit(0) has been there for a while,
some other accounting bugs made it very difficult to trigger,
so it is reasonable to replace. In particular, see golang.org/issue/7711#c10
for an examination of the behavior across past releases.
Fixes #7711.
LGTM=iant, r
R=golang-codereviews, iant, dvyukov, r
CC=golang-codereviews
https://golang.org/cl/88210044
2014-04-16 11:12:18 -06:00
|
|
|
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)
|
2014-04-15 09:48:17 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-27 22:00:01 -06:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-16 02:19:33 -06:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-05-29 23:10:54 -06:00
|
|
|
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")
|
|
|
|
}
|
|
|
|
`
|
2013-02-20 01:15:02 -07:00
|
|
|
|
|
|
|
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 {}
|
|
|
|
}
|
|
|
|
`
|
2013-03-05 00:40:17 -07:00
|
|
|
|
|
|
|
const goexitDeadlockSource = `
|
|
|
|
package main
|
|
|
|
import (
|
|
|
|
"runtime"
|
|
|
|
)
|
|
|
|
|
|
|
|
func F() {
|
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
go F()
|
|
|
|
go F()
|
|
|
|
runtime.Goexit()
|
|
|
|
}
|
|
|
|
`
|
2013-08-15 20:34:06 -06:00
|
|
|
|
|
|
|
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[:])
|
|
|
|
}
|
|
|
|
`
|
2013-08-16 20:25:26 -06:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`
|
2014-03-07 09:50:30 -07:00
|
|
|
|
|
|
|
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")
|
|
|
|
}
|
|
|
|
`
|
2014-04-15 09:48:17 -06:00
|
|
|
|
|
|
|
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()
|
|
|
|
}
|
|
|
|
`
|
2014-05-27 22:00:01 -06:00
|
|
|
|
|
|
|
const goNilSource = `
|
|
|
|
package main
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
defer func() {
|
|
|
|
recover()
|
|
|
|
}()
|
|
|
|
var f func()
|
|
|
|
go f()
|
|
|
|
select{}
|
|
|
|
}
|
|
|
|
`
|
2014-07-16 02:19:33 -06:00
|
|
|
|
|
|
|
const mainGoroutineIdSource = `
|
|
|
|
package main
|
|
|
|
func main() {
|
|
|
|
panic("test")
|
|
|
|
}
|
|
|
|
`
|