Home | History | Annotate | Download | only in runtime
      1 // Copyright 2012 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 // +build darwin dragonfly freebsd linux netbsd openbsd solaris
      6 
      7 package runtime_test
      8 
      9 import (
     10 	"bytes"
     11 	"internal/testenv"
     12 	"io"
     13 	"io/ioutil"
     14 	"os"
     15 	"os/exec"
     16 	"path/filepath"
     17 	"runtime"
     18 	"strings"
     19 	"syscall"
     20 	"testing"
     21 )
     22 
     23 // sigquit is the signal to send to kill a hanging testdata program.
     24 // Send SIGQUIT to get a stack trace.
     25 var sigquit = syscall.SIGQUIT
     26 
     27 func init() {
     28 	if runtime.Sigisblocked(int(syscall.SIGQUIT)) {
     29 		// We can't use SIGQUIT to kill subprocesses because
     30 		// it's blocked. Use SIGKILL instead. See issue
     31 		// #19196 for an example of when this happens.
     32 		sigquit = syscall.SIGKILL
     33 	}
     34 }
     35 
     36 func TestCrashDumpsAllThreads(t *testing.T) {
     37 	switch runtime.GOOS {
     38 	case "darwin", "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "solaris":
     39 	default:
     40 		t.Skipf("skipping; not supported on %v", runtime.GOOS)
     41 	}
     42 
     43 	if runtime.Sigisblocked(int(syscall.SIGQUIT)) {
     44 		t.Skip("skipping; SIGQUIT is blocked, see golang.org/issue/19196")
     45 	}
     46 
     47 	// We don't use executeTest because we need to kill the
     48 	// program while it is running.
     49 
     50 	testenv.MustHaveGoBuild(t)
     51 
     52 	checkStaleRuntime(t)
     53 
     54 	t.Parallel()
     55 
     56 	dir, err := ioutil.TempDir("", "go-build")
     57 	if err != nil {
     58 		t.Fatalf("failed to create temp directory: %v", err)
     59 	}
     60 	defer os.RemoveAll(dir)
     61 
     62 	if err := ioutil.WriteFile(filepath.Join(dir, "main.go"), []byte(crashDumpsAllThreadsSource), 0666); err != nil {
     63 		t.Fatalf("failed to create Go file: %v", err)
     64 	}
     65 
     66 	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe")
     67 	cmd.Dir = dir
     68 	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
     69 	if err != nil {
     70 		t.Fatalf("building source: %v\n%s", err, out)
     71 	}
     72 
     73 	cmd = exec.Command(filepath.Join(dir, "a.exe"))
     74 	cmd = testenv.CleanCmdEnv(cmd)
     75 	cmd.Env = append(cmd.Env, "GOTRACEBACK=crash")
     76 
     77 	// Set GOGC=off. Because of golang.org/issue/10958, the tight
     78 	// loops in the test program are not preemptible. If GC kicks
     79 	// in, it may lock up and prevent main from saying it's ready.
     80 	newEnv := []string{}
     81 	for _, s := range cmd.Env {
     82 		if !strings.HasPrefix(s, "GOGC=") {
     83 			newEnv = append(newEnv, s)
     84 		}
     85 	}
     86 	cmd.Env = append(newEnv, "GOGC=off")
     87 
     88 	var outbuf bytes.Buffer
     89 	cmd.Stdout = &outbuf
     90 	cmd.Stderr = &outbuf
     91 
     92 	rp, wp, err := os.Pipe()
     93 	if err != nil {
     94 		t.Fatal(err)
     95 	}
     96 	cmd.ExtraFiles = []*os.File{wp}
     97 
     98 	if err := cmd.Start(); err != nil {
     99 		t.Fatalf("starting program: %v", err)
    100 	}
    101 
    102 	if err := wp.Close(); err != nil {
    103 		t.Logf("closing write pipe: %v", err)
    104 	}
    105 	if _, err := rp.Read(make([]byte, 1)); err != nil {
    106 		t.Fatalf("reading from pipe: %v", err)
    107 	}
    108 
    109 	if err := cmd.Process.Signal(syscall.SIGQUIT); err != nil {
    110 		t.Fatalf("signal: %v", err)
    111 	}
    112 
    113 	// No point in checking the error return from Wait--we expect
    114 	// it to fail.
    115 	cmd.Wait()
    116 
    117 	// We want to see a stack trace for each thread.
    118 	// Before https://golang.org/cl/2811 running threads would say
    119 	// "goroutine running on other thread; stack unavailable".
    120 	out = outbuf.Bytes()
    121 	n := bytes.Count(out, []byte("main.loop("))
    122 	if n != 4 {
    123 		t.Errorf("found %d instances of main.loop; expected 4", n)
    124 		t.Logf("%s", out)
    125 	}
    126 }
    127 
    128 const crashDumpsAllThreadsSource = `
    129 package main
    130 
    131 import (
    132 	"fmt"
    133 	"os"
    134 	"runtime"
    135 )
    136 
    137 func main() {
    138 	const count = 4
    139 	runtime.GOMAXPROCS(count + 1)
    140 
    141 	chans := make([]chan bool, count)
    142 	for i := range chans {
    143 		chans[i] = make(chan bool)
    144 		go loop(i, chans[i])
    145 	}
    146 
    147 	// Wait for all the goroutines to start executing.
    148 	for _, c := range chans {
    149 		<-c
    150 	}
    151 
    152 	// Tell our parent that all the goroutines are executing.
    153 	if _, err := os.NewFile(3, "pipe").WriteString("x"); err != nil {
    154 		fmt.Fprintf(os.Stderr, "write to pipe failed: %v\n", err)
    155 		os.Exit(2)
    156 	}
    157 
    158 	select {}
    159 }
    160 
    161 func loop(i int, c chan bool) {
    162 	close(c)
    163 	for {
    164 		for j := 0; j < 0x7fffffff; j++ {
    165 		}
    166 	}
    167 }
    168 `
    169 
    170 func TestPanicSystemstack(t *testing.T) {
    171 	// Test that GOTRACEBACK=crash prints both the system and user
    172 	// stack of other threads.
    173 
    174 	// The GOTRACEBACK=crash handler takes 0.1 seconds even if
    175 	// it's not writing a core file and potentially much longer if
    176 	// it is. Skip in short mode.
    177 	if testing.Short() {
    178 		t.Skip("Skipping in short mode (GOTRACEBACK=crash is slow)")
    179 	}
    180 
    181 	if runtime.Sigisblocked(int(syscall.SIGQUIT)) {
    182 		t.Skip("skipping; SIGQUIT is blocked, see golang.org/issue/19196")
    183 	}
    184 
    185 	t.Parallel()
    186 	cmd := exec.Command(os.Args[0], "testPanicSystemstackInternal")
    187 	cmd = testenv.CleanCmdEnv(cmd)
    188 	cmd.Env = append(cmd.Env, "GOTRACEBACK=crash")
    189 	pr, pw, err := os.Pipe()
    190 	if err != nil {
    191 		t.Fatal("creating pipe: ", err)
    192 	}
    193 	cmd.Stderr = pw
    194 	if err := cmd.Start(); err != nil {
    195 		t.Fatal("starting command: ", err)
    196 	}
    197 	defer cmd.Process.Wait()
    198 	defer cmd.Process.Kill()
    199 	if err := pw.Close(); err != nil {
    200 		t.Log("closing write pipe: ", err)
    201 	}
    202 	defer pr.Close()
    203 
    204 	// Wait for "x\nx\n" to indicate readiness.
    205 	buf := make([]byte, 4)
    206 	_, err = io.ReadFull(pr, buf)
    207 	if err != nil || string(buf) != "x\nx\n" {
    208 		t.Fatal("subprocess failed; output:\n", string(buf))
    209 	}
    210 
    211 	// Send SIGQUIT.
    212 	if err := cmd.Process.Signal(syscall.SIGQUIT); err != nil {
    213 		t.Fatal("signaling subprocess: ", err)
    214 	}
    215 
    216 	// Get traceback.
    217 	tb, err := ioutil.ReadAll(pr)
    218 	if err != nil {
    219 		t.Fatal("reading traceback from pipe: ", err)
    220 	}
    221 
    222 	// Traceback should have two testPanicSystemstackInternal's
    223 	// and two blockOnSystemStackInternal's.
    224 	if bytes.Count(tb, []byte("testPanicSystemstackInternal")) != 2 {
    225 		t.Fatal("traceback missing user stack:\n", string(tb))
    226 	} else if bytes.Count(tb, []byte("blockOnSystemStackInternal")) != 2 {
    227 		t.Fatal("traceback missing system stack:\n", string(tb))
    228 	}
    229 }
    230 
    231 func init() {
    232 	if len(os.Args) >= 2 && os.Args[1] == "testPanicSystemstackInternal" {
    233 		// Get two threads running on the system stack with
    234 		// something recognizable in the stack trace.
    235 		runtime.GOMAXPROCS(2)
    236 		go testPanicSystemstackInternal()
    237 		testPanicSystemstackInternal()
    238 	}
    239 }
    240 
    241 func testPanicSystemstackInternal() {
    242 	runtime.BlockOnSystemStack()
    243 	os.Exit(1) // Should be unreachable.
    244 }
    245 
    246 func TestSignalExitStatus(t *testing.T) {
    247 	testenv.MustHaveGoBuild(t)
    248 	exe, err := buildTestProg(t, "testprog")
    249 	if err != nil {
    250 		t.Fatal(err)
    251 	}
    252 	err = testenv.CleanCmdEnv(exec.Command(exe, "SignalExitStatus")).Run()
    253 	if err == nil {
    254 		t.Error("test program succeeded unexpectedly")
    255 	} else if ee, ok := err.(*exec.ExitError); !ok {
    256 		t.Errorf("error (%v) has type %T; expected exec.ExitError", err, err)
    257 	} else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok {
    258 		t.Errorf("error.Sys (%v) has type %T; expected syscall.WaitStatus", ee.Sys(), ee.Sys())
    259 	} else if !ws.Signaled() || ws.Signal() != syscall.SIGTERM {
    260 		t.Errorf("got %v; expected SIGTERM", ee)
    261 	}
    262 }
    263 
    264 func TestSignalIgnoreSIGTRAP(t *testing.T) {
    265 	output := runTestProg(t, "testprognet", "SignalIgnoreSIGTRAP")
    266 	want := "OK\n"
    267 	if output != want {
    268 		t.Fatalf("want %s, got %s\n", want, output)
    269 	}
    270 }
    271 
    272 func TestSignalDuringExec(t *testing.T) {
    273 	switch runtime.GOOS {
    274 	case "darwin", "dragonfly", "freebsd", "linux", "netbsd", "openbsd":
    275 	default:
    276 		t.Skipf("skipping test on %s", runtime.GOOS)
    277 	}
    278 	output := runTestProg(t, "testprognet", "SignalDuringExec")
    279 	want := "OK\n"
    280 	if output != want {
    281 		t.Fatalf("want %s, got %s\n", want, output)
    282 	}
    283 }
    284