| // 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 unix |
| |
| 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). |
| // |
| // See https://golang.org/issue/33174. |
| settleTime = 5 * 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.Println("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 { |
| tc := tc |
| t.Run(tc.name, func(t *testing.T) { |
| t.Parallel() |
| |
| var subTimeout time.Duration |
| if deadline, ok := t.Deadline(); ok { |
| timeout := time.Until(deadline) |
| if timeout < 2*settleTime { |
| t.Fatalf("starting test with less than %v remaining", 2*settleTime) |
| } |
| subTimeout = timeout - (timeout / 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\n"); !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 |
| } |