913 lines
27 KiB
Go
913 lines
27 KiB
Go
// Copyright 2009 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.
|
|
|
|
//go:build aix || darwin || dragonfly || freebsd || hurd || linux || netbsd || openbsd || solaris
|
|
|
|
package signal
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"internal/testenv"
|
|
"os"
|
|
"os/exec"
|
|
"runtime"
|
|
"runtime/trace"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// settleTime is an upper bound on how long we expect signals to take to be
|
|
// delivered. Lower values make the test faster, but also flakier — especially
|
|
// on heavily loaded systems.
|
|
//
|
|
// The current value is set based on flakes observed in the Go builders.
|
|
var settleTime = 100 * time.Millisecond
|
|
|
|
// fatalWaitingTime is an absurdly long time to wait for signals to be
|
|
// delivered but, using it, we (hopefully) eliminate test flakes on the
|
|
// build servers. See #46736 for discussion.
|
|
var fatalWaitingTime = 30 * time.Second
|
|
|
|
func init() {
|
|
if testenv.Builder() == "solaris-amd64-oraclerel" {
|
|
// The solaris-amd64-oraclerel builder has been observed to time out in
|
|
// TestNohup even with a 250ms settle time.
|
|
//
|
|
// Use a much longer settle time on that builder to try to suss out whether
|
|
// the test is flaky due to builder slowness (which may mean we need a
|
|
// longer GO_TEST_TIMEOUT_SCALE) or due to a dropped signal (which may
|
|
// instead need a test-skip and upstream bug filed against the Solaris
|
|
// kernel).
|
|
//
|
|
// This constant is chosen so as to make the test as generous as possible
|
|
// while still reliably completing within 3 minutes in non-short mode.
|
|
//
|
|
// See https://golang.org/issue/33174.
|
|
settleTime = 11 * time.Second
|
|
} else if runtime.GOOS == "linux" && strings.HasPrefix(runtime.GOARCH, "ppc64") {
|
|
// Older linux kernels seem to have some hiccups delivering the signal
|
|
// in a timely manner on ppc64 and ppc64le. When running on a
|
|
// ppc64le/ubuntu 16.04/linux 4.4 host the time can vary quite
|
|
// substantially even on a idle system. 5 seconds is twice any value
|
|
// observed when running 10000 tests on such a system.
|
|
settleTime = 5 * time.Second
|
|
} else if s := os.Getenv("GO_TEST_TIMEOUT_SCALE"); s != "" {
|
|
if scale, err := strconv.Atoi(s); err == nil {
|
|
settleTime *= time.Duration(scale)
|
|
}
|
|
}
|
|
}
|
|
|
|
func waitSig(t *testing.T, c <-chan os.Signal, sig os.Signal) {
|
|
t.Helper()
|
|
waitSig1(t, c, sig, false)
|
|
}
|
|
func waitSigAll(t *testing.T, c <-chan os.Signal, sig os.Signal) {
|
|
t.Helper()
|
|
waitSig1(t, c, sig, true)
|
|
}
|
|
|
|
func waitSig1(t *testing.T, c <-chan os.Signal, sig os.Signal, all bool) {
|
|
t.Helper()
|
|
|
|
// Sleep multiple times to give the kernel more tries to
|
|
// deliver the signal.
|
|
start := time.Now()
|
|
timer := time.NewTimer(settleTime / 10)
|
|
defer timer.Stop()
|
|
// If the caller notified for all signals on c, filter out SIGURG,
|
|
// which is used for runtime preemption and can come at unpredictable times.
|
|
// General user code should filter out all unexpected signals instead of just
|
|
// SIGURG, but since os/signal is tightly coupled to the runtime it seems
|
|
// appropriate to be stricter here.
|
|
for time.Since(start) < fatalWaitingTime {
|
|
select {
|
|
case s := <-c:
|
|
if s == sig {
|
|
return
|
|
}
|
|
if !all || s != syscall.SIGURG {
|
|
t.Fatalf("signal was %v, want %v", s, sig)
|
|
}
|
|
case <-timer.C:
|
|
timer.Reset(settleTime / 10)
|
|
}
|
|
}
|
|
t.Fatalf("timeout after %v waiting for %v", fatalWaitingTime, sig)
|
|
}
|
|
|
|
// quiesce waits until we can be reasonably confident that all pending signals
|
|
// have been delivered by the OS.
|
|
func quiesce() {
|
|
// The kernel will deliver a signal as a thread returns
|
|
// from a syscall. If the only active thread is sleeping,
|
|
// and the system is busy, the kernel may not get around
|
|
// to waking up a thread to catch the signal.
|
|
// We try splitting up the sleep to give the kernel
|
|
// many chances to deliver the signal.
|
|
start := time.Now()
|
|
for time.Since(start) < settleTime {
|
|
time.Sleep(settleTime / 10)
|
|
}
|
|
}
|
|
|
|
// Test that basic signal handling works.
|
|
func TestSignal(t *testing.T) {
|
|
// Ask for SIGHUP
|
|
c := make(chan os.Signal, 1)
|
|
Notify(c, syscall.SIGHUP)
|
|
defer Stop(c)
|
|
|
|
// Send this process a SIGHUP
|
|
t.Logf("sighup...")
|
|
syscall.Kill(syscall.Getpid(), syscall.SIGHUP)
|
|
waitSig(t, c, syscall.SIGHUP)
|
|
|
|
// Ask for everything we can get. The buffer size has to be
|
|
// more than 1, since the runtime might send SIGURG signals.
|
|
// Using 10 is arbitrary.
|
|
c1 := make(chan os.Signal, 10)
|
|
Notify(c1)
|
|
// Stop relaying the SIGURG signals. See #49724
|
|
Reset(syscall.SIGURG)
|
|
defer Stop(c1)
|
|
|
|
// Send this process a SIGWINCH
|
|
t.Logf("sigwinch...")
|
|
syscall.Kill(syscall.Getpid(), syscall.SIGWINCH)
|
|
waitSigAll(t, c1, syscall.SIGWINCH)
|
|
|
|
// Send two more SIGHUPs, to make sure that
|
|
// they get delivered on c1 and that not reading
|
|
// from c does not block everything.
|
|
t.Logf("sighup...")
|
|
syscall.Kill(syscall.Getpid(), syscall.SIGHUP)
|
|
waitSigAll(t, c1, syscall.SIGHUP)
|
|
t.Logf("sighup...")
|
|
syscall.Kill(syscall.Getpid(), syscall.SIGHUP)
|
|
waitSigAll(t, c1, syscall.SIGHUP)
|
|
|
|
// The first SIGHUP should be waiting for us on c.
|
|
waitSig(t, c, syscall.SIGHUP)
|
|
}
|
|
|
|
func TestStress(t *testing.T) {
|
|
dur := 3 * time.Second
|
|
if testing.Short() {
|
|
dur = 100 * time.Millisecond
|
|
}
|
|
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4))
|
|
|
|
sig := make(chan os.Signal, 1)
|
|
Notify(sig, syscall.SIGUSR1)
|
|
|
|
go func() {
|
|
stop := time.After(dur)
|
|
for {
|
|
select {
|
|
case <-stop:
|
|
// Allow enough time for all signals to be delivered before we stop
|
|
// listening for them.
|
|
quiesce()
|
|
Stop(sig)
|
|
// According to its documentation, “[w]hen Stop returns, it in
|
|
// guaranteed that c will receive no more signals.” So we can safely
|
|
// close sig here: if there is a send-after-close race here, that is a
|
|
// bug in Stop and we would like to detect it.
|
|
close(sig)
|
|
return
|
|
|
|
default:
|
|
syscall.Kill(syscall.Getpid(), syscall.SIGUSR1)
|
|
runtime.Gosched()
|
|
}
|
|
}
|
|
}()
|
|
|
|
for range sig {
|
|
// Receive signals until the sender closes sig.
|
|
}
|
|
}
|
|
|
|
func testCancel(t *testing.T, ignore bool) {
|
|
// Ask to be notified on c1 when a SIGWINCH is received.
|
|
c1 := make(chan os.Signal, 1)
|
|
Notify(c1, syscall.SIGWINCH)
|
|
defer Stop(c1)
|
|
|
|
// Ask to be notified on c2 when a SIGHUP is received.
|
|
c2 := make(chan os.Signal, 1)
|
|
Notify(c2, syscall.SIGHUP)
|
|
defer Stop(c2)
|
|
|
|
// Send this process a SIGWINCH and wait for notification on c1.
|
|
syscall.Kill(syscall.Getpid(), syscall.SIGWINCH)
|
|
waitSig(t, c1, syscall.SIGWINCH)
|
|
|
|
// Send this process a SIGHUP and wait for notification on c2.
|
|
syscall.Kill(syscall.Getpid(), syscall.SIGHUP)
|
|
waitSig(t, c2, syscall.SIGHUP)
|
|
|
|
// Ignore, or reset the signal handlers for, SIGWINCH and SIGHUP.
|
|
// Either way, this should undo both calls to Notify above.
|
|
if ignore {
|
|
Ignore(syscall.SIGWINCH, syscall.SIGHUP)
|
|
// Don't bother deferring a call to Reset: it is documented to undo Notify,
|
|
// but its documentation says nothing about Ignore, and (as of the time of
|
|
// writing) it empirically does not undo an Ignore.
|
|
} else {
|
|
Reset(syscall.SIGWINCH, syscall.SIGHUP)
|
|
}
|
|
|
|
// Send this process a SIGWINCH. It should be ignored.
|
|
syscall.Kill(syscall.Getpid(), syscall.SIGWINCH)
|
|
|
|
// If ignoring, Send this process a SIGHUP. It should be ignored.
|
|
if ignore {
|
|
syscall.Kill(syscall.Getpid(), syscall.SIGHUP)
|
|
}
|
|
|
|
quiesce()
|
|
|
|
select {
|
|
case s := <-c1:
|
|
t.Errorf("unexpected signal %v", s)
|
|
default:
|
|
// nothing to read - good
|
|
}
|
|
|
|
select {
|
|
case s := <-c2:
|
|
t.Errorf("unexpected signal %v", s)
|
|
default:
|
|
// nothing to read - good
|
|
}
|
|
|
|
// One or both of the signals may have been blocked for this process
|
|
// by the calling process.
|
|
// Discard any queued signals now to avoid interfering with other tests.
|
|
Notify(c1, syscall.SIGWINCH)
|
|
Notify(c2, syscall.SIGHUP)
|
|
quiesce()
|
|
}
|
|
|
|
// Test that Reset cancels registration for listed signals on all channels.
|
|
func TestReset(t *testing.T) {
|
|
testCancel(t, false)
|
|
}
|
|
|
|
// Test that Ignore cancels registration for listed signals on all channels.
|
|
func TestIgnore(t *testing.T) {
|
|
testCancel(t, true)
|
|
}
|
|
|
|
// Test that Ignored correctly detects changes to the ignored status of a signal.
|
|
func TestIgnored(t *testing.T) {
|
|
// Ask to be notified on SIGWINCH.
|
|
c := make(chan os.Signal, 1)
|
|
Notify(c, syscall.SIGWINCH)
|
|
|
|
// If we're being notified, then the signal should not be ignored.
|
|
if Ignored(syscall.SIGWINCH) {
|
|
t.Errorf("expected SIGWINCH to not be ignored.")
|
|
}
|
|
Stop(c)
|
|
Ignore(syscall.SIGWINCH)
|
|
|
|
// We're no longer paying attention to this signal.
|
|
if !Ignored(syscall.SIGWINCH) {
|
|
t.Errorf("expected SIGWINCH to be ignored when explicitly ignoring it.")
|
|
}
|
|
|
|
Reset()
|
|
}
|
|
|
|
var checkSighupIgnored = flag.Bool("check_sighup_ignored", false, "if true, TestDetectNohup will fail if SIGHUP is not ignored.")
|
|
|
|
// Test that Ignored(SIGHUP) correctly detects whether it is being run under nohup.
|
|
func TestDetectNohup(t *testing.T) {
|
|
if *checkSighupIgnored {
|
|
if !Ignored(syscall.SIGHUP) {
|
|
t.Fatal("SIGHUP is not ignored.")
|
|
} else {
|
|
t.Log("SIGHUP is ignored.")
|
|
}
|
|
} else {
|
|
defer Reset()
|
|
// Ugly: ask for SIGHUP so that child will not have no-hup set
|
|
// even if test is running under nohup environment.
|
|
// We have no intention of reading from c.
|
|
c := make(chan os.Signal, 1)
|
|
Notify(c, syscall.SIGHUP)
|
|
if out, err := exec.Command(os.Args[0], "-test.run=TestDetectNohup", "-check_sighup_ignored").CombinedOutput(); err == nil {
|
|
t.Errorf("ran test with -check_sighup_ignored and it succeeded: expected failure.\nOutput:\n%s", out)
|
|
}
|
|
Stop(c)
|
|
// Again, this time with nohup, assuming we can find it.
|
|
_, err := os.Stat("/usr/bin/nohup")
|
|
if err != nil {
|
|
t.Skip("cannot find nohup; skipping second half of test")
|
|
}
|
|
Ignore(syscall.SIGHUP)
|
|
os.Remove("nohup.out")
|
|
out, err := exec.Command("/usr/bin/nohup", os.Args[0], "-test.run=TestDetectNohup", "-check_sighup_ignored").CombinedOutput()
|
|
|
|
data, _ := os.ReadFile("nohup.out")
|
|
os.Remove("nohup.out")
|
|
if err != nil {
|
|
t.Errorf("ran test with -check_sighup_ignored under nohup and it failed: expected success.\nError: %v\nOutput:\n%s%s", err, out, data)
|
|
}
|
|
}
|
|
}
|
|
|
|
var (
|
|
sendUncaughtSighup = flag.Int("send_uncaught_sighup", 0, "send uncaught SIGHUP during TestStop")
|
|
dieFromSighup = flag.Bool("die_from_sighup", false, "wait to die from uncaught SIGHUP")
|
|
)
|
|
|
|
// Test that Stop cancels the channel's registrations.
|
|
func TestStop(t *testing.T) {
|
|
sigs := []syscall.Signal{
|
|
syscall.SIGWINCH,
|
|
syscall.SIGHUP,
|
|
syscall.SIGUSR1,
|
|
}
|
|
|
|
for _, sig := range sigs {
|
|
sig := sig
|
|
t.Run(fmt.Sprint(sig), func(t *testing.T) {
|
|
// When calling Notify with a specific signal,
|
|
// independent signals should not interfere with each other,
|
|
// and we end up needing to wait for signals to quiesce a lot.
|
|
// Test the three different signals concurrently.
|
|
t.Parallel()
|
|
|
|
// If the signal is not ignored, send the signal before registering a
|
|
// channel to verify the behavior of the default Go handler.
|
|
// If it's SIGWINCH or SIGUSR1 we should not see it.
|
|
// If it's SIGHUP, maybe we'll die. Let the flag tell us what to do.
|
|
mayHaveBlockedSignal := false
|
|
if !Ignored(sig) && (sig != syscall.SIGHUP || *sendUncaughtSighup == 1) {
|
|
syscall.Kill(syscall.Getpid(), sig)
|
|
quiesce()
|
|
|
|
// We don't know whether sig is blocked for this process; see
|
|
// https://golang.org/issue/38165. Assume that it could be.
|
|
mayHaveBlockedSignal = true
|
|
}
|
|
|
|
// Ask for signal
|
|
c := make(chan os.Signal, 1)
|
|
Notify(c, sig)
|
|
|
|
// Send this process the signal again.
|
|
syscall.Kill(syscall.Getpid(), sig)
|
|
waitSig(t, c, sig)
|
|
|
|
if mayHaveBlockedSignal {
|
|
// We may have received a queued initial signal in addition to the one
|
|
// that we sent after Notify. If so, waitSig may have observed that
|
|
// initial signal instead of the second one, and we may need to wait for
|
|
// the second signal to clear. Do that now.
|
|
quiesce()
|
|
select {
|
|
case <-c:
|
|
default:
|
|
}
|
|
}
|
|
|
|
// Stop watching for the signal and send it again.
|
|
// If it's SIGHUP, maybe we'll die. Let the flag tell us what to do.
|
|
Stop(c)
|
|
if sig != syscall.SIGHUP || *sendUncaughtSighup == 2 {
|
|
syscall.Kill(syscall.Getpid(), sig)
|
|
quiesce()
|
|
|
|
select {
|
|
case s := <-c:
|
|
t.Errorf("unexpected signal %v", s)
|
|
default:
|
|
// nothing to read - good
|
|
}
|
|
|
|
// If we're going to receive a signal, it has almost certainly been
|
|
// received by now. However, it may have been blocked for this process —
|
|
// we don't know. Explicitly unblock it and wait for it to clear now.
|
|
Notify(c, sig)
|
|
quiesce()
|
|
Stop(c)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Test that when run under nohup, an uncaught SIGHUP does not kill the program.
|
|
func TestNohup(t *testing.T) {
|
|
// Ugly: ask for SIGHUP so that child will not have no-hup set
|
|
// even if test is running under nohup environment.
|
|
// We have no intention of reading from c.
|
|
c := make(chan os.Signal, 1)
|
|
Notify(c, syscall.SIGHUP)
|
|
|
|
// When run without nohup, the test should crash on an uncaught SIGHUP.
|
|
// When run under nohup, the test should ignore uncaught SIGHUPs,
|
|
// because the runtime is not supposed to be listening for them.
|
|
// Either way, TestStop should still be able to catch them when it wants them
|
|
// and then when it stops wanting them, the original behavior should resume.
|
|
//
|
|
// send_uncaught_sighup=1 sends the SIGHUP before starting to listen for SIGHUPs.
|
|
// send_uncaught_sighup=2 sends the SIGHUP after no longer listening for SIGHUPs.
|
|
//
|
|
// Both should fail without nohup and succeed with nohup.
|
|
|
|
var subTimeout time.Duration
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(2)
|
|
if deadline, ok := t.Deadline(); ok {
|
|
subTimeout = time.Until(deadline)
|
|
subTimeout -= subTimeout / 10 // Leave 10% headroom for propagating output.
|
|
}
|
|
for i := 1; i <= 2; i++ {
|
|
i := i
|
|
go t.Run(fmt.Sprintf("uncaught-%d", i), func(t *testing.T) {
|
|
defer wg.Done()
|
|
|
|
args := []string{
|
|
"-test.v",
|
|
"-test.run=TestStop",
|
|
"-send_uncaught_sighup=" + strconv.Itoa(i),
|
|
"-die_from_sighup",
|
|
}
|
|
if subTimeout != 0 {
|
|
args = append(args, fmt.Sprintf("-test.timeout=%v", subTimeout))
|
|
}
|
|
out, err := exec.Command(os.Args[0], args...).CombinedOutput()
|
|
|
|
if err == nil {
|
|
t.Errorf("ran test with -send_uncaught_sighup=%d and it succeeded: expected failure.\nOutput:\n%s", i, out)
|
|
} else {
|
|
t.Logf("test with -send_uncaught_sighup=%d failed as expected.\nError: %v\nOutput:\n%s", i, err, out)
|
|
}
|
|
})
|
|
}
|
|
wg.Wait()
|
|
|
|
Stop(c)
|
|
|
|
// Skip the nohup test below when running in tmux on darwin, since nohup
|
|
// doesn't work correctly there. See issue #5135.
|
|
if runtime.GOOS == "darwin" && os.Getenv("TMUX") != "" {
|
|
t.Skip("Skipping nohup test due to running in tmux on darwin")
|
|
}
|
|
|
|
// Again, this time with nohup, assuming we can find it.
|
|
_, err := exec.LookPath("nohup")
|
|
if err != nil {
|
|
t.Skip("cannot find nohup; skipping second half of test")
|
|
}
|
|
|
|
wg.Add(2)
|
|
if deadline, ok := t.Deadline(); ok {
|
|
subTimeout = time.Until(deadline)
|
|
subTimeout -= subTimeout / 10 // Leave 10% headroom for propagating output.
|
|
}
|
|
for i := 1; i <= 2; i++ {
|
|
i := i
|
|
go t.Run(fmt.Sprintf("nohup-%d", i), func(t *testing.T) {
|
|
defer wg.Done()
|
|
|
|
// POSIX specifies that nohup writes to a file named nohup.out if standard
|
|
// output is a terminal. However, for an exec.Command, standard output is
|
|
// not a terminal — so we don't need to read or remove that file (and,
|
|
// indeed, cannot even create it if the current user is unable to write to
|
|
// GOROOT/src, such as when GOROOT is installed and owned by root).
|
|
|
|
args := []string{
|
|
os.Args[0],
|
|
"-test.v",
|
|
"-test.run=TestStop",
|
|
"-send_uncaught_sighup=" + strconv.Itoa(i),
|
|
}
|
|
if subTimeout != 0 {
|
|
args = append(args, fmt.Sprintf("-test.timeout=%v", subTimeout))
|
|
}
|
|
out, err := exec.Command("nohup", args...).CombinedOutput()
|
|
|
|
if err != nil {
|
|
t.Errorf("ran test with -send_uncaught_sighup=%d under nohup and it failed: expected success.\nError: %v\nOutput:\n%s", i, err, out)
|
|
} else {
|
|
t.Logf("ran test with -send_uncaught_sighup=%d under nohup.\nOutput:\n%s", i, out)
|
|
}
|
|
})
|
|
}
|
|
wg.Wait()
|
|
}
|
|
|
|
// Test that SIGCONT works (issue 8953).
|
|
func TestSIGCONT(t *testing.T) {
|
|
c := make(chan os.Signal, 1)
|
|
Notify(c, syscall.SIGCONT)
|
|
defer Stop(c)
|
|
syscall.Kill(syscall.Getpid(), syscall.SIGCONT)
|
|
waitSig(t, c, syscall.SIGCONT)
|
|
}
|
|
|
|
// Test race between stopping and receiving a signal (issue 14571).
|
|
func TestAtomicStop(t *testing.T) {
|
|
if os.Getenv("GO_TEST_ATOMIC_STOP") != "" {
|
|
atomicStopTestProgram(t)
|
|
t.Fatal("atomicStopTestProgram returned")
|
|
}
|
|
|
|
testenv.MustHaveExec(t)
|
|
|
|
// Call Notify for SIGINT before starting the child process.
|
|
// That ensures that SIGINT is not ignored for the child.
|
|
// This is necessary because if SIGINT is ignored when a
|
|
// Go program starts, then it remains ignored, and closing
|
|
// the last notification channel for SIGINT will switch it
|
|
// back to being ignored. In that case the assumption of
|
|
// atomicStopTestProgram, that it will either die from SIGINT
|
|
// or have it be reported, breaks down, as there is a third
|
|
// option: SIGINT might be ignored.
|
|
cs := make(chan os.Signal, 1)
|
|
Notify(cs, syscall.SIGINT)
|
|
defer Stop(cs)
|
|
|
|
const execs = 10
|
|
for i := 0; i < execs; i++ {
|
|
timeout := "0"
|
|
if deadline, ok := t.Deadline(); ok {
|
|
timeout = time.Until(deadline).String()
|
|
}
|
|
cmd := exec.Command(os.Args[0], "-test.run=TestAtomicStop", "-test.timeout="+timeout)
|
|
cmd.Env = append(os.Environ(), "GO_TEST_ATOMIC_STOP=1")
|
|
out, err := cmd.CombinedOutput()
|
|
if err == nil {
|
|
if len(out) > 0 {
|
|
t.Logf("iteration %d: output %s", i, out)
|
|
}
|
|
} else {
|
|
t.Logf("iteration %d: exit status %q: output: %s", i, err, out)
|
|
}
|
|
|
|
lost := bytes.Contains(out, []byte("lost signal"))
|
|
if lost {
|
|
t.Errorf("iteration %d: lost signal", i)
|
|
}
|
|
|
|
// The program should either die due to SIGINT,
|
|
// or exit with success without printing "lost signal".
|
|
if err == nil {
|
|
if len(out) > 0 && !lost {
|
|
t.Errorf("iteration %d: unexpected output", i)
|
|
}
|
|
} else {
|
|
if ee, ok := err.(*exec.ExitError); !ok {
|
|
t.Errorf("iteration %d: error (%v) has type %T; expected exec.ExitError", i, err, err)
|
|
} else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok {
|
|
t.Errorf("iteration %d: error.Sys (%v) has type %T; expected syscall.WaitStatus", i, ee.Sys(), ee.Sys())
|
|
} else if !ws.Signaled() || ws.Signal() != syscall.SIGINT {
|
|
t.Errorf("iteration %d: got exit status %v; expected SIGINT", i, ee)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// atomicStopTestProgram is run in a subprocess by TestAtomicStop.
|
|
// It tries to trigger a signal delivery race. This function should
|
|
// either catch a signal or die from it.
|
|
func atomicStopTestProgram(t *testing.T) {
|
|
// This test won't work if SIGINT is ignored here.
|
|
if Ignored(syscall.SIGINT) {
|
|
fmt.Println("SIGINT is ignored")
|
|
os.Exit(1)
|
|
}
|
|
|
|
const tries = 10
|
|
|
|
timeout := 2 * time.Second
|
|
if deadline, ok := t.Deadline(); ok {
|
|
// Give each try an equal slice of the deadline, with one slice to spare for
|
|
// cleanup.
|
|
timeout = time.Until(deadline) / (tries + 1)
|
|
}
|
|
|
|
pid := syscall.Getpid()
|
|
printed := false
|
|
for i := 0; i < tries; i++ {
|
|
cs := make(chan os.Signal, 1)
|
|
Notify(cs, syscall.SIGINT)
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
Stop(cs)
|
|
}()
|
|
|
|
syscall.Kill(pid, syscall.SIGINT)
|
|
|
|
// At this point we should either die from SIGINT or
|
|
// get a notification on cs. If neither happens, we
|
|
// dropped the signal. It is given 2 seconds to
|
|
// deliver, as needed for gccgo on some loaded test systems.
|
|
|
|
select {
|
|
case <-cs:
|
|
case <-time.After(timeout):
|
|
if !printed {
|
|
fmt.Print("lost signal on tries:")
|
|
printed = true
|
|
}
|
|
fmt.Printf(" %d", i)
|
|
}
|
|
|
|
wg.Wait()
|
|
}
|
|
if printed {
|
|
fmt.Print("\n")
|
|
}
|
|
|
|
os.Exit(0)
|
|
}
|
|
|
|
func TestTime(t *testing.T) {
|
|
// Test that signal works fine when we are in a call to get time,
|
|
// which on some platforms is using VDSO. See issue #34391.
|
|
dur := 3 * time.Second
|
|
if testing.Short() {
|
|
dur = 100 * time.Millisecond
|
|
}
|
|
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4))
|
|
|
|
sig := make(chan os.Signal, 1)
|
|
Notify(sig, syscall.SIGUSR1)
|
|
|
|
stop := make(chan struct{})
|
|
go func() {
|
|
for {
|
|
select {
|
|
case <-stop:
|
|
// Allow enough time for all signals to be delivered before we stop
|
|
// listening for them.
|
|
quiesce()
|
|
Stop(sig)
|
|
// According to its documentation, “[w]hen Stop returns, it in
|
|
// guaranteed that c will receive no more signals.” So we can safely
|
|
// close sig here: if there is a send-after-close race, that is a bug in
|
|
// Stop and we would like to detect it.
|
|
close(sig)
|
|
return
|
|
|
|
default:
|
|
syscall.Kill(syscall.Getpid(), syscall.SIGUSR1)
|
|
runtime.Gosched()
|
|
}
|
|
}
|
|
}()
|
|
|
|
done := make(chan struct{})
|
|
go func() {
|
|
for range sig {
|
|
// Receive signals until the sender closes sig.
|
|
}
|
|
close(done)
|
|
}()
|
|
|
|
t0 := time.Now()
|
|
for t1 := t0; t1.Sub(t0) < dur; t1 = time.Now() {
|
|
} // hammering on getting time
|
|
|
|
close(stop)
|
|
<-done
|
|
}
|
|
|
|
var (
|
|
checkNotifyContext = flag.Bool("check_notify_ctx", false, "if true, TestNotifyContext will fail if SIGINT is not received.")
|
|
ctxNotifyTimes = flag.Int("ctx_notify_times", 1, "number of times a SIGINT signal should be received")
|
|
)
|
|
|
|
func TestNotifyContextNotifications(t *testing.T) {
|
|
if *checkNotifyContext {
|
|
ctx, _ := NotifyContext(context.Background(), syscall.SIGINT)
|
|
// We want to make sure not to be calling Stop() internally on NotifyContext() when processing a received signal.
|
|
// Being able to wait for a number of received system signals allows us to do so.
|
|
var wg sync.WaitGroup
|
|
n := *ctxNotifyTimes
|
|
wg.Add(n)
|
|
for i := 0; i < n; i++ {
|
|
go func() {
|
|
syscall.Kill(syscall.Getpid(), syscall.SIGINT)
|
|
wg.Done()
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
<-ctx.Done()
|
|
fmt.Print("received SIGINT")
|
|
// Sleep to give time to simultaneous signals to reach the process.
|
|
// These signals must be ignored given stop() is not called on this code.
|
|
// We want to guarantee a SIGINT doesn't cause a premature termination of the program.
|
|
time.Sleep(settleTime)
|
|
return
|
|
}
|
|
|
|
t.Parallel()
|
|
testCases := []struct {
|
|
name string
|
|
n int // number of times a SIGINT should be notified.
|
|
}{
|
|
{"once", 1},
|
|
{"multiple", 10},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
var subTimeout time.Duration
|
|
if deadline, ok := t.Deadline(); ok {
|
|
subTimeout := time.Until(deadline)
|
|
subTimeout -= subTimeout / 10 // Leave 10% headroom for cleaning up subprocess.
|
|
}
|
|
|
|
args := []string{
|
|
"-test.v",
|
|
"-test.run=TestNotifyContextNotifications$",
|
|
"-check_notify_ctx",
|
|
fmt.Sprintf("-ctx_notify_times=%d", tc.n),
|
|
}
|
|
if subTimeout != 0 {
|
|
args = append(args, fmt.Sprintf("-test.timeout=%v", subTimeout))
|
|
}
|
|
out, err := exec.Command(os.Args[0], args...).CombinedOutput()
|
|
if err != nil {
|
|
t.Errorf("ran test with -check_notify_ctx_notification and it failed with %v.\nOutput:\n%s", err, out)
|
|
}
|
|
if want := []byte("received SIGINT"); !bytes.Contains(out, want) {
|
|
t.Errorf("got %q, wanted %q", out, want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNotifyContextStop(t *testing.T) {
|
|
Ignore(syscall.SIGHUP)
|
|
if !Ignored(syscall.SIGHUP) {
|
|
t.Errorf("expected SIGHUP to be ignored when explicitly ignoring it.")
|
|
}
|
|
|
|
parent, cancelParent := context.WithCancel(context.Background())
|
|
defer cancelParent()
|
|
c, stop := NotifyContext(parent, syscall.SIGHUP)
|
|
defer stop()
|
|
|
|
// If we're being notified, then the signal should not be ignored.
|
|
if Ignored(syscall.SIGHUP) {
|
|
t.Errorf("expected SIGHUP to not be ignored.")
|
|
}
|
|
|
|
if want, got := "signal.NotifyContext(context.Background.WithCancel, [hangup])", fmt.Sprint(c); want != got {
|
|
t.Errorf("c.String() = %q, wanted %q", got, want)
|
|
}
|
|
|
|
stop()
|
|
select {
|
|
case <-c.Done():
|
|
if got := c.Err(); got != context.Canceled {
|
|
t.Errorf("c.Err() = %q, want %q", got, context.Canceled)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Errorf("timed out waiting for context to be done after calling stop")
|
|
}
|
|
}
|
|
|
|
func TestNotifyContextCancelParent(t *testing.T) {
|
|
parent, cancelParent := context.WithCancel(context.Background())
|
|
defer cancelParent()
|
|
c, stop := NotifyContext(parent, syscall.SIGINT)
|
|
defer stop()
|
|
|
|
if want, got := "signal.NotifyContext(context.Background.WithCancel, [interrupt])", fmt.Sprint(c); want != got {
|
|
t.Errorf("c.String() = %q, want %q", got, want)
|
|
}
|
|
|
|
cancelParent()
|
|
select {
|
|
case <-c.Done():
|
|
if got := c.Err(); got != context.Canceled {
|
|
t.Errorf("c.Err() = %q, want %q", got, context.Canceled)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Errorf("timed out waiting for parent context to be canceled")
|
|
}
|
|
}
|
|
|
|
func TestNotifyContextPrematureCancelParent(t *testing.T) {
|
|
parent, cancelParent := context.WithCancel(context.Background())
|
|
defer cancelParent()
|
|
|
|
cancelParent() // Prematurely cancel context before calling NotifyContext.
|
|
c, stop := NotifyContext(parent, syscall.SIGINT)
|
|
defer stop()
|
|
|
|
if want, got := "signal.NotifyContext(context.Background.WithCancel, [interrupt])", fmt.Sprint(c); want != got {
|
|
t.Errorf("c.String() = %q, want %q", got, want)
|
|
}
|
|
|
|
select {
|
|
case <-c.Done():
|
|
if got := c.Err(); got != context.Canceled {
|
|
t.Errorf("c.Err() = %q, want %q", got, context.Canceled)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Errorf("timed out waiting for parent context to be canceled")
|
|
}
|
|
}
|
|
|
|
func TestNotifyContextSimultaneousStop(t *testing.T) {
|
|
c, stop := NotifyContext(context.Background(), syscall.SIGINT)
|
|
defer stop()
|
|
|
|
if want, got := "signal.NotifyContext(context.Background, [interrupt])", fmt.Sprint(c); want != got {
|
|
t.Errorf("c.String() = %q, want %q", got, want)
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
n := 10
|
|
wg.Add(n)
|
|
for i := 0; i < n; i++ {
|
|
go func() {
|
|
stop()
|
|
wg.Done()
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
select {
|
|
case <-c.Done():
|
|
if got := c.Err(); got != context.Canceled {
|
|
t.Errorf("c.Err() = %q, want %q", got, context.Canceled)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Errorf("expected context to be canceled")
|
|
}
|
|
}
|
|
|
|
func TestNotifyContextStringer(t *testing.T) {
|
|
parent, cancelParent := context.WithCancel(context.Background())
|
|
defer cancelParent()
|
|
c, stop := NotifyContext(parent, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
|
|
defer stop()
|
|
|
|
want := `signal.NotifyContext(context.Background.WithCancel, [hangup interrupt terminated])`
|
|
if got := fmt.Sprint(c); got != want {
|
|
t.Errorf("c.String() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
// #44193 test signal handling while stopping and starting the world.
|
|
func TestSignalTrace(t *testing.T) {
|
|
done := make(chan struct{})
|
|
quit := make(chan struct{})
|
|
c := make(chan os.Signal, 1)
|
|
Notify(c, syscall.SIGHUP)
|
|
|
|
// Source and sink for signals busy loop unsynchronized with
|
|
// trace starts and stops. We are ultimately validating that
|
|
// signals and runtime.(stop|start)TheWorldGC are compatible.
|
|
go func() {
|
|
defer close(done)
|
|
defer Stop(c)
|
|
pid := syscall.Getpid()
|
|
for {
|
|
select {
|
|
case <-quit:
|
|
return
|
|
default:
|
|
syscall.Kill(pid, syscall.SIGHUP)
|
|
}
|
|
waitSig(t, c, syscall.SIGHUP)
|
|
}
|
|
}()
|
|
|
|
for i := 0; i < 100; i++ {
|
|
buf := new(bytes.Buffer)
|
|
if err := trace.Start(buf); err != nil {
|
|
t.Fatalf("[%d] failed to start tracing: %v", i, err)
|
|
}
|
|
time.After(1 * time.Microsecond)
|
|
trace.Stop()
|
|
size := buf.Len()
|
|
if size == 0 {
|
|
t.Fatalf("[%d] trace is empty", i)
|
|
}
|
|
}
|
|
close(quit)
|
|
<-done
|
|
}
|