summaryrefslogtreecommitdiffstats
path: root/src/os/signal
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:19:13 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:19:13 +0000
commitccd992355df7192993c666236047820244914598 (patch)
treef00fea65147227b7743083c6148396f74cd66935 /src/os/signal
parentInitial commit. (diff)
downloadgolang-1.21-ccd992355df7192993c666236047820244914598.tar.xz
golang-1.21-ccd992355df7192993c666236047820244914598.zip
Adding upstream version 1.21.8.upstream/1.21.8
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/os/signal')
-rw-r--r--src/os/signal/doc.go232
-rw-r--r--src/os/signal/example_test.go38
-rw-r--r--src/os/signal/example_unix_test.go47
-rw-r--r--src/os/signal/sig.s8
-rw-r--r--src/os/signal/signal.go334
-rw-r--r--src/os/signal/signal_cgo_test.go350
-rw-r--r--src/os/signal/signal_linux_test.go42
-rw-r--r--src/os/signal/signal_plan9.go64
-rw-r--r--src/os/signal/signal_plan9_test.go167
-rw-r--r--src/os/signal/signal_test.go932
-rw-r--r--src/os/signal/signal_unix.go62
-rw-r--r--src/os/signal/signal_windows_test.go98
12 files changed, 2374 insertions, 0 deletions
diff --git a/src/os/signal/doc.go b/src/os/signal/doc.go
new file mode 100644
index 0000000..a2a7525
--- /dev/null
+++ b/src/os/signal/doc.go
@@ -0,0 +1,232 @@
+// Copyright 2015 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 signal implements access to incoming signals.
+
+Signals are primarily used on Unix-like systems. For the use of this
+package on Windows and Plan 9, see below.
+
+# Types of signals
+
+The signals SIGKILL and SIGSTOP may not be caught by a program, and
+therefore cannot be affected by this package.
+
+Synchronous signals are signals triggered by errors in program
+execution: SIGBUS, SIGFPE, and SIGSEGV. These are only considered
+synchronous when caused by program execution, not when sent using
+[os.Process.Kill] or the kill program or some similar mechanism. In
+general, except as discussed below, Go programs will convert a
+synchronous signal into a run-time panic.
+
+The remaining signals are asynchronous signals. They are not
+triggered by program errors, but are instead sent from the kernel or
+from some other program.
+
+Of the asynchronous signals, the SIGHUP signal is sent when a program
+loses its controlling terminal. The SIGINT signal is sent when the
+user at the controlling terminal presses the interrupt character,
+which by default is ^C (Control-C). The SIGQUIT signal is sent when
+the user at the controlling terminal presses the quit character, which
+by default is ^\ (Control-Backslash). In general you can cause a
+program to simply exit by pressing ^C, and you can cause it to exit
+with a stack dump by pressing ^\.
+
+# Default behavior of signals in Go programs
+
+By default, a synchronous signal is converted into a run-time panic. A
+SIGHUP, SIGINT, or SIGTERM signal causes the program to exit. A
+SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGSTKFLT, SIGEMT, or SIGSYS signal
+causes the program to exit with a stack dump. A SIGTSTP, SIGTTIN, or
+SIGTTOU signal gets the system default behavior (these signals are
+used by the shell for job control). The SIGPROF signal is handled
+directly by the Go runtime to implement runtime.CPUProfile. Other
+signals will be caught but no action will be taken.
+
+If the Go program is started with either SIGHUP or SIGINT ignored
+(signal handler set to SIG_IGN), they will remain ignored.
+
+If the Go program is started with a non-empty signal mask, that will
+generally be honored. However, some signals are explicitly unblocked:
+the synchronous signals, SIGILL, SIGTRAP, SIGSTKFLT, SIGCHLD, SIGPROF,
+and, on Linux, signals 32 (SIGCANCEL) and 33 (SIGSETXID)
+(SIGCANCEL and SIGSETXID are used internally by glibc). Subprocesses
+started by [os.Exec], or by [os/exec], will inherit the
+modified signal mask.
+
+# Changing the behavior of signals in Go programs
+
+The functions in this package allow a program to change the way Go
+programs handle signals.
+
+Notify disables the default behavior for a given set of asynchronous
+signals and instead delivers them over one or more registered
+channels. Specifically, it applies to the signals SIGHUP, SIGINT,
+SIGQUIT, SIGABRT, and SIGTERM. It also applies to the job control
+signals SIGTSTP, SIGTTIN, and SIGTTOU, in which case the system
+default behavior does not occur. It also applies to some signals that
+otherwise cause no action: SIGUSR1, SIGUSR2, SIGPIPE, SIGALRM,
+SIGCHLD, SIGCONT, SIGURG, SIGXCPU, SIGXFSZ, SIGVTALRM, SIGWINCH,
+SIGIO, SIGPWR, SIGSYS, SIGINFO, SIGTHR, SIGWAITING, SIGLWP, SIGFREEZE,
+SIGTHAW, SIGLOST, SIGXRES, SIGJVM1, SIGJVM2, and any real time signals
+used on the system. Note that not all of these signals are available
+on all systems.
+
+If the program was started with SIGHUP or SIGINT ignored, and Notify
+is called for either signal, a signal handler will be installed for
+that signal and it will no longer be ignored. If, later, Reset or
+Ignore is called for that signal, or Stop is called on all channels
+passed to Notify for that signal, the signal will once again be
+ignored. Reset will restore the system default behavior for the
+signal, while Ignore will cause the system to ignore the signal
+entirely.
+
+If the program is started with a non-empty signal mask, some signals
+will be explicitly unblocked as described above. If Notify is called
+for a blocked signal, it will be unblocked. If, later, Reset is
+called for that signal, or Stop is called on all channels passed to
+Notify for that signal, the signal will once again be blocked.
+
+# SIGPIPE
+
+When a Go program writes to a broken pipe, the kernel will raise a
+SIGPIPE signal.
+
+If the program has not called Notify to receive SIGPIPE signals, then
+the behavior depends on the file descriptor number. A write to a
+broken pipe on file descriptors 1 or 2 (standard output or standard
+error) will cause the program to exit with a SIGPIPE signal. A write
+to a broken pipe on some other file descriptor will take no action on
+the SIGPIPE signal, and the write will fail with an EPIPE error.
+
+If the program has called Notify to receive SIGPIPE signals, the file
+descriptor number does not matter. The SIGPIPE signal will be
+delivered to the Notify channel, and the write will fail with an EPIPE
+error.
+
+This means that, by default, command line programs will behave like
+typical Unix command line programs, while other programs will not
+crash with SIGPIPE when writing to a closed network connection.
+
+# Go programs that use cgo or SWIG
+
+In a Go program that includes non-Go code, typically C/C++ code
+accessed using cgo or SWIG, Go's startup code normally runs first. It
+configures the signal handlers as expected by the Go runtime, before
+the non-Go startup code runs. If the non-Go startup code wishes to
+install its own signal handlers, it must take certain steps to keep Go
+working well. This section documents those steps and the overall
+effect changes to signal handler settings by the non-Go code can have
+on Go programs. In rare cases, the non-Go code may run before the Go
+code, in which case the next section also applies.
+
+If the non-Go code called by the Go program does not change any signal
+handlers or masks, then the behavior is the same as for a pure Go
+program.
+
+If the non-Go code installs any signal handlers, it must use the
+SA_ONSTACK flag with sigaction. Failing to do so is likely to cause
+the program to crash if the signal is received. Go programs routinely
+run with a limited stack, and therefore set up an alternate signal
+stack.
+
+If the non-Go code installs a signal handler for any of the
+synchronous signals (SIGBUS, SIGFPE, SIGSEGV), then it should record
+the existing Go signal handler. If those signals occur while
+executing Go code, it should invoke the Go signal handler (whether the
+signal occurs while executing Go code can be determined by looking at
+the PC passed to the signal handler). Otherwise some Go run-time
+panics will not occur as expected.
+
+If the non-Go code installs a signal handler for any of the
+asynchronous signals, it may invoke the Go signal handler or not as it
+chooses. Naturally, if it does not invoke the Go signal handler, the
+Go behavior described above will not occur. This can be an issue with
+the SIGPROF signal in particular.
+
+The non-Go code should not change the signal mask on any threads
+created by the Go runtime. If the non-Go code starts new threads of
+its own, it may set the signal mask as it pleases.
+
+If the non-Go code starts a new thread, changes the signal mask, and
+then invokes a Go function in that thread, the Go runtime will
+automatically unblock certain signals: the synchronous signals,
+SIGILL, SIGTRAP, SIGSTKFLT, SIGCHLD, SIGPROF, SIGCANCEL, and
+SIGSETXID. When the Go function returns, the non-Go signal mask will
+be restored.
+
+If the Go signal handler is invoked on a non-Go thread not running Go
+code, the handler generally forwards the signal to the non-Go code, as
+follows. If the signal is SIGPROF, the Go handler does
+nothing. Otherwise, the Go handler removes itself, unblocks the
+signal, and raises it again, to invoke any non-Go handler or default
+system handler. If the program does not exit, the Go handler then
+reinstalls itself and continues execution of the program.
+
+If a SIGPIPE signal is received, the Go program will invoke the
+special handling described above if the SIGPIPE is received on a Go
+thread. If the SIGPIPE is received on a non-Go thread the signal will
+be forwarded to the non-Go handler, if any; if there is none the
+default system handler will cause the program to terminate.
+
+# Non-Go programs that call Go code
+
+When Go code is built with options like -buildmode=c-shared, it will
+be run as part of an existing non-Go program. The non-Go code may
+have already installed signal handlers when the Go code starts (that
+may also happen in unusual cases when using cgo or SWIG; in that case,
+the discussion here applies). For -buildmode=c-archive the Go runtime
+will initialize signals at global constructor time. For
+-buildmode=c-shared the Go runtime will initialize signals when the
+shared library is loaded.
+
+If the Go runtime sees an existing signal handler for the SIGCANCEL or
+SIGSETXID signals (which are used only on Linux), it will turn on
+the SA_ONSTACK flag and otherwise keep the signal handler.
+
+For the synchronous signals and SIGPIPE, the Go runtime will install a
+signal handler. It will save any existing signal handler. If a
+synchronous signal arrives while executing non-Go code, the Go runtime
+will invoke the existing signal handler instead of the Go signal
+handler.
+
+Go code built with -buildmode=c-archive or -buildmode=c-shared will
+not install any other signal handlers by default. If there is an
+existing signal handler, the Go runtime will turn on the SA_ONSTACK
+flag and otherwise keep the signal handler. If Notify is called for an
+asynchronous signal, a Go signal handler will be installed for that
+signal. If, later, Reset is called for that signal, the original
+handling for that signal will be reinstalled, restoring the non-Go
+signal handler if any.
+
+Go code built without -buildmode=c-archive or -buildmode=c-shared will
+install a signal handler for the asynchronous signals listed above,
+and save any existing signal handler. If a signal is delivered to a
+non-Go thread, it will act as described above, except that if there is
+an existing non-Go signal handler, that handler will be installed
+before raising the signal.
+
+# Windows
+
+On Windows a ^C (Control-C) or ^BREAK (Control-Break) normally cause
+the program to exit. If Notify is called for [os.Interrupt], ^C or ^BREAK
+will cause [os.Interrupt] to be sent on the channel, and the program will
+not exit. If Reset is called, or Stop is called on all channels passed
+to Notify, then the default behavior will be restored.
+
+Additionally, if Notify is called, and Windows sends CTRL_CLOSE_EVENT,
+CTRL_LOGOFF_EVENT or CTRL_SHUTDOWN_EVENT to the process, Notify will
+return syscall.SIGTERM. Unlike Control-C and Control-Break, Notify does
+not change process behavior when either CTRL_CLOSE_EVENT,
+CTRL_LOGOFF_EVENT or CTRL_SHUTDOWN_EVENT is received - the process will
+still get terminated unless it exits. But receiving syscall.SIGTERM will
+give the process an opportunity to clean up before termination.
+
+# Plan 9
+
+On Plan 9, signals have type syscall.Note, which is a string. Calling
+Notify with a syscall.Note will cause that value to be sent on the
+channel when that string is posted as a note.
+*/
+package signal
diff --git a/src/os/signal/example_test.go b/src/os/signal/example_test.go
new file mode 100644
index 0000000..ecefc75
--- /dev/null
+++ b/src/os/signal/example_test.go
@@ -0,0 +1,38 @@
+// Copyright 2013 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 signal_test
+
+import (
+ "fmt"
+ "os"
+ "os/signal"
+)
+
+func ExampleNotify() {
+ // Set up channel on which to send signal notifications.
+ // We must use a buffered channel or risk missing the signal
+ // if we're not ready to receive when the signal is sent.
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, os.Interrupt)
+
+ // Block until a signal is received.
+ s := <-c
+ fmt.Println("Got signal:", s)
+}
+
+func ExampleNotify_allSignals() {
+ // Set up channel on which to send signal notifications.
+ // We must use a buffered channel or risk missing the signal
+ // if we're not ready to receive when the signal is sent.
+ c := make(chan os.Signal, 1)
+
+ // Passing no signals to Notify means that
+ // all signals will be sent to the channel.
+ signal.Notify(c)
+
+ // Block until any signal is received.
+ s := <-c
+ fmt.Println("Got signal:", s)
+}
diff --git a/src/os/signal/example_unix_test.go b/src/os/signal/example_unix_test.go
new file mode 100644
index 0000000..b7047ac
--- /dev/null
+++ b/src/os/signal/example_unix_test.go
@@ -0,0 +1,47 @@
+// Copyright 2020 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_test
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "os"
+ "os/signal"
+ "time"
+)
+
+// This example passes a context with a signal to tell a blocking function that
+// it should abandon its work after a signal is received.
+func ExampleNotifyContext() {
+ ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
+ defer stop()
+
+ p, err := os.FindProcess(os.Getpid())
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // On a Unix-like system, pressing Ctrl+C on a keyboard sends a
+ // SIGINT signal to the process of the program in execution.
+ //
+ // This example simulates that by sending a SIGINT signal to itself.
+ if err := p.Signal(os.Interrupt); err != nil {
+ log.Fatal(err)
+ }
+
+ select {
+ case <-time.After(time.Second):
+ fmt.Println("missed signal")
+ case <-ctx.Done():
+ fmt.Println(ctx.Err()) // prints "context canceled"
+ stop() // stop receiving signal notifications as soon as possible.
+ }
+
+ // Output:
+ // context canceled
+}
diff --git a/src/os/signal/sig.s b/src/os/signal/sig.s
new file mode 100644
index 0000000..12833a8
--- /dev/null
+++ b/src/os/signal/sig.s
@@ -0,0 +1,8 @@
+// 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.
+
+// The runtime package uses //go:linkname to push a few functions into this
+// package but we still need a .s file so the Go tool does not pass -complete
+// to the go tool compile so the latter does not complain about Go functions
+// with no bodies.
diff --git a/src/os/signal/signal.go b/src/os/signal/signal.go
new file mode 100644
index 0000000..4250a7e
--- /dev/null
+++ b/src/os/signal/signal.go
@@ -0,0 +1,334 @@
+// 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 signal
+
+import (
+ "context"
+ "os"
+ "sync"
+)
+
+var handlers struct {
+ sync.Mutex
+ // Map a channel to the signals that should be sent to it.
+ m map[chan<- os.Signal]*handler
+ // Map a signal to the number of channels receiving it.
+ ref [numSig]int64
+ // Map channels to signals while the channel is being stopped.
+ // Not a map because entries live here only very briefly.
+ // We need a separate container because we need m to correspond to ref
+ // at all times, and we also need to keep track of the *handler
+ // value for a channel being stopped. See the Stop function.
+ stopping []stopping
+}
+
+type stopping struct {
+ c chan<- os.Signal
+ h *handler
+}
+
+type handler struct {
+ mask [(numSig + 31) / 32]uint32
+}
+
+func (h *handler) want(sig int) bool {
+ return (h.mask[sig/32]>>uint(sig&31))&1 != 0
+}
+
+func (h *handler) set(sig int) {
+ h.mask[sig/32] |= 1 << uint(sig&31)
+}
+
+func (h *handler) clear(sig int) {
+ h.mask[sig/32] &^= 1 << uint(sig&31)
+}
+
+// Stop relaying the signals, sigs, to any channels previously registered to
+// receive them and either reset the signal handlers to their original values
+// (action=disableSignal) or ignore the signals (action=ignoreSignal).
+func cancel(sigs []os.Signal, action func(int)) {
+ handlers.Lock()
+ defer handlers.Unlock()
+
+ remove := func(n int) {
+ var zerohandler handler
+
+ for c, h := range handlers.m {
+ if h.want(n) {
+ handlers.ref[n]--
+ h.clear(n)
+ if h.mask == zerohandler.mask {
+ delete(handlers.m, c)
+ }
+ }
+ }
+
+ action(n)
+ }
+
+ if len(sigs) == 0 {
+ for n := 0; n < numSig; n++ {
+ remove(n)
+ }
+ } else {
+ for _, s := range sigs {
+ remove(signum(s))
+ }
+ }
+}
+
+// Ignore causes the provided signals to be ignored. If they are received by
+// the program, nothing will happen. Ignore undoes the effect of any prior
+// calls to Notify for the provided signals.
+// If no signals are provided, all incoming signals will be ignored.
+func Ignore(sig ...os.Signal) {
+ cancel(sig, ignoreSignal)
+}
+
+// Ignored reports whether sig is currently ignored.
+func Ignored(sig os.Signal) bool {
+ sn := signum(sig)
+ return sn >= 0 && signalIgnored(sn)
+}
+
+var (
+ // watchSignalLoopOnce guards calling the conditionally
+ // initialized watchSignalLoop. If watchSignalLoop is non-nil,
+ // it will be run in a goroutine lazily once Notify is invoked.
+ // See Issue 21576.
+ watchSignalLoopOnce sync.Once
+ watchSignalLoop func()
+)
+
+// Notify causes package signal to relay incoming signals to c.
+// If no signals are provided, all incoming signals will be relayed to c.
+// Otherwise, just the provided signals will.
+//
+// Package signal will not block sending to c: the caller must ensure
+// that c has sufficient buffer space to keep up with the expected
+// signal rate. For a channel used for notification of just one signal value,
+// a buffer of size 1 is sufficient.
+//
+// It is allowed to call Notify multiple times with the same channel:
+// each call expands the set of signals sent to that channel.
+// The only way to remove signals from the set is to call Stop.
+//
+// It is allowed to call Notify multiple times with different channels
+// and the same signals: each channel receives copies of incoming
+// signals independently.
+func Notify(c chan<- os.Signal, sig ...os.Signal) {
+ if c == nil {
+ panic("os/signal: Notify using nil channel")
+ }
+
+ handlers.Lock()
+ defer handlers.Unlock()
+
+ h := handlers.m[c]
+ if h == nil {
+ if handlers.m == nil {
+ handlers.m = make(map[chan<- os.Signal]*handler)
+ }
+ h = new(handler)
+ handlers.m[c] = h
+ }
+
+ add := func(n int) {
+ if n < 0 {
+ return
+ }
+ if !h.want(n) {
+ h.set(n)
+ if handlers.ref[n] == 0 {
+ enableSignal(n)
+
+ // The runtime requires that we enable a
+ // signal before starting the watcher.
+ watchSignalLoopOnce.Do(func() {
+ if watchSignalLoop != nil {
+ go watchSignalLoop()
+ }
+ })
+ }
+ handlers.ref[n]++
+ }
+ }
+
+ if len(sig) == 0 {
+ for n := 0; n < numSig; n++ {
+ add(n)
+ }
+ } else {
+ for _, s := range sig {
+ add(signum(s))
+ }
+ }
+}
+
+// Reset undoes the effect of any prior calls to Notify for the provided
+// signals.
+// If no signals are provided, all signal handlers will be reset.
+func Reset(sig ...os.Signal) {
+ cancel(sig, disableSignal)
+}
+
+// Stop causes package signal to stop relaying incoming signals to c.
+// It undoes the effect of all prior calls to Notify using c.
+// When Stop returns, it is guaranteed that c will receive no more signals.
+func Stop(c chan<- os.Signal) {
+ handlers.Lock()
+
+ h := handlers.m[c]
+ if h == nil {
+ handlers.Unlock()
+ return
+ }
+ delete(handlers.m, c)
+
+ for n := 0; n < numSig; n++ {
+ if h.want(n) {
+ handlers.ref[n]--
+ if handlers.ref[n] == 0 {
+ disableSignal(n)
+ }
+ }
+ }
+
+ // Signals will no longer be delivered to the channel.
+ // We want to avoid a race for a signal such as SIGINT:
+ // it should be either delivered to the channel,
+ // or the program should take the default action (that is, exit).
+ // To avoid the possibility that the signal is delivered,
+ // and the signal handler invoked, and then Stop deregisters
+ // the channel before the process function below has a chance
+ // to send it on the channel, put the channel on a list of
+ // channels being stopped and wait for signal delivery to
+ // quiesce before fully removing it.
+
+ handlers.stopping = append(handlers.stopping, stopping{c, h})
+
+ handlers.Unlock()
+
+ signalWaitUntilIdle()
+
+ handlers.Lock()
+
+ for i, s := range handlers.stopping {
+ if s.c == c {
+ handlers.stopping = append(handlers.stopping[:i], handlers.stopping[i+1:]...)
+ break
+ }
+ }
+
+ handlers.Unlock()
+}
+
+// Wait until there are no more signals waiting to be delivered.
+// Defined by the runtime package.
+func signalWaitUntilIdle()
+
+func process(sig os.Signal) {
+ n := signum(sig)
+ if n < 0 {
+ return
+ }
+
+ handlers.Lock()
+ defer handlers.Unlock()
+
+ for c, h := range handlers.m {
+ if h.want(n) {
+ // send but do not block for it
+ select {
+ case c <- sig:
+ default:
+ }
+ }
+ }
+
+ // Avoid the race mentioned in Stop.
+ for _, d := range handlers.stopping {
+ if d.h.want(n) {
+ select {
+ case d.c <- sig:
+ default:
+ }
+ }
+ }
+}
+
+// NotifyContext returns a copy of the parent context that is marked done
+// (its Done channel is closed) when one of the listed signals arrives,
+// when the returned stop function is called, or when the parent context's
+// Done channel is closed, whichever happens first.
+//
+// The stop function unregisters the signal behavior, which, like signal.Reset,
+// may restore the default behavior for a given signal. For example, the default
+// behavior of a Go program receiving os.Interrupt is to exit. Calling
+// NotifyContext(parent, os.Interrupt) will change the behavior to cancel
+// the returned context. Future interrupts received will not trigger the default
+// (exit) behavior until the returned stop function is called.
+//
+// The stop function releases resources associated with it, so code should
+// call stop as soon as the operations running in this Context complete and
+// signals no longer need to be diverted to the context.
+func NotifyContext(parent context.Context, signals ...os.Signal) (ctx context.Context, stop context.CancelFunc) {
+ ctx, cancel := context.WithCancel(parent)
+ c := &signalCtx{
+ Context: ctx,
+ cancel: cancel,
+ signals: signals,
+ }
+ c.ch = make(chan os.Signal, 1)
+ Notify(c.ch, c.signals...)
+ if ctx.Err() == nil {
+ go func() {
+ select {
+ case <-c.ch:
+ c.cancel()
+ case <-c.Done():
+ }
+ }()
+ }
+ return c, c.stop
+}
+
+type signalCtx struct {
+ context.Context
+
+ cancel context.CancelFunc
+ signals []os.Signal
+ ch chan os.Signal
+}
+
+func (c *signalCtx) stop() {
+ c.cancel()
+ Stop(c.ch)
+}
+
+type stringer interface {
+ String() string
+}
+
+func (c *signalCtx) String() string {
+ var buf []byte
+ // We know that the type of c.Context is context.cancelCtx, and we know that the
+ // String method of cancelCtx returns a string that ends with ".WithCancel".
+ name := c.Context.(stringer).String()
+ name = name[:len(name)-len(".WithCancel")]
+ buf = append(buf, "signal.NotifyContext("+name...)
+ if len(c.signals) != 0 {
+ buf = append(buf, ", ["...)
+ for i, s := range c.signals {
+ buf = append(buf, s.String()...)
+ if i != len(c.signals)-1 {
+ buf = append(buf, ' ')
+ }
+ }
+ buf = append(buf, ']')
+ }
+ buf = append(buf, ')')
+ return string(buf)
+}
diff --git a/src/os/signal/signal_cgo_test.go b/src/os/signal/signal_cgo_test.go
new file mode 100644
index 0000000..ac59215
--- /dev/null
+++ b/src/os/signal/signal_cgo_test.go
@@ -0,0 +1,350 @@
+// Copyright 2017 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 (darwin || dragonfly || freebsd || (linux && !android) || netbsd || openbsd) && cgo
+
+// Note that this test does not work on Solaris: issue #22849.
+// Don't run the test on Android because at least some versions of the
+// C library do not define the posix_openpt function.
+
+package signal_test
+
+import (
+ "context"
+ "encoding/binary"
+ "fmt"
+ "internal/testpty"
+ "os"
+ "os/exec"
+ "os/signal"
+ "runtime"
+ "strconv"
+ "syscall"
+ "testing"
+ "time"
+ "unsafe"
+)
+
+const (
+ ptyFD = 3 // child end of pty.
+ controlFD = 4 // child end of control pipe.
+)
+
+// TestTerminalSignal tests that read from a pseudo-terminal does not return an
+// error if the process is SIGSTOP'd and put in the background during the read.
+//
+// This test simulates stopping a Go process running in a shell with ^Z and
+// then resuming with `fg`.
+//
+// This is a regression test for https://go.dev/issue/22838. On Darwin, PTY
+// reads return EINTR when this occurs, and Go should automatically retry.
+func TestTerminalSignal(t *testing.T) {
+ // This test simulates stopping a Go process running in a shell with ^Z
+ // and then resuming with `fg`. This sounds simple, but is actually
+ // quite complicated.
+ //
+ // In principle, what we are doing is:
+ // 1. Creating a new PTY parent/child FD pair.
+ // 2. Create a child that is in the foreground process group of the PTY, and read() from that process.
+ // 3. Stop the child with ^Z.
+ // 4. Take over as foreground process group of the PTY from the parent.
+ // 5. Make the child foreground process group again.
+ // 6. Continue the child.
+ //
+ // On Darwin, step 4 results in the read() returning EINTR once the
+ // process continues. internal/poll should automatically retry the
+ // read.
+ //
+ // These steps are complicated by the rules around foreground process
+ // groups. A process group cannot be foreground if it is "orphaned",
+ // unless it masks SIGTTOU. i.e., to be foreground the process group
+ // must have a parent process group in the same session or mask SIGTTOU
+ // (which we do). An orphaned process group cannot receive
+ // terminal-generated SIGTSTP at all.
+ //
+ // Achieving this requires three processes total:
+ // - Top-level process: this is the main test process and creates the
+ // pseudo-terminal.
+ // - GO_TEST_TERMINAL_SIGNALS=1: This process creates a new process
+ // group and session. The PTY is the controlling terminal for this
+ // session. This process masks SIGTTOU, making it eligible to be a
+ // foreground process group. This process will take over as foreground
+ // from subprocess 2 (step 4 above).
+ // - GO_TEST_TERMINAL_SIGNALS=2: This process create a child process
+ // group of subprocess 1, and is the original foreground process group
+ // for the PTY. This subprocess is the one that is SIGSTOP'd.
+
+ if runtime.GOOS == "dragonfly" {
+ t.Skip("skipping: wait hangs on dragonfly; see https://go.dev/issue/56132")
+ }
+
+ scale := 1
+ if s := os.Getenv("GO_TEST_TIMEOUT_SCALE"); s != "" {
+ if sc, err := strconv.Atoi(s); err == nil {
+ scale = sc
+ }
+ }
+ pause := time.Duration(scale) * 10 * time.Millisecond
+
+ lvl := os.Getenv("GO_TEST_TERMINAL_SIGNALS")
+ switch lvl {
+ case "":
+ // Main test process, run code below.
+ break
+ case "1":
+ runSessionLeader(pause)
+ panic("unreachable")
+ case "2":
+ runStoppingChild()
+ panic("unreachable")
+ default:
+ fmt.Fprintf(os.Stderr, "unknown subprocess level %s\n", lvl)
+ os.Exit(1)
+ }
+
+ t.Parallel()
+
+ pty, procTTYName, err := testpty.Open()
+ if err != nil {
+ ptyErr := err.(*testpty.PtyError)
+ if ptyErr.FuncName == "posix_openpt" && ptyErr.Errno == syscall.EACCES {
+ t.Skip("posix_openpt failed with EACCES, assuming chroot and skipping")
+ }
+ t.Fatal(err)
+ }
+ defer pty.Close()
+ procTTY, err := os.OpenFile(procTTYName, os.O_RDWR, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer procTTY.Close()
+
+ // Control pipe. GO_TEST_TERMINAL_SIGNALS=2 send the PID of
+ // GO_TEST_TERMINAL_SIGNALS=3 here. After SIGSTOP, it also writes a
+ // byte to indicate that the foreground cycling is complete.
+ controlR, controlW, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), 100*time.Second)
+ defer cancel()
+ cmd := exec.CommandContext(ctx, os.Args[0], "-test.run=TestTerminalSignal")
+ cmd.Env = append(os.Environ(), "GO_TEST_TERMINAL_SIGNALS=1")
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout // for logging
+ cmd.Stderr = os.Stderr
+ cmd.ExtraFiles = []*os.File{procTTY, controlW}
+ cmd.SysProcAttr = &syscall.SysProcAttr{
+ Setsid: true,
+ Setctty: true,
+ Ctty: ptyFD,
+ }
+
+ if err := cmd.Start(); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := procTTY.Close(); err != nil {
+ t.Errorf("closing procTTY: %v", err)
+ }
+
+ if err := controlW.Close(); err != nil {
+ t.Errorf("closing controlW: %v", err)
+ }
+
+ // Wait for first child to send the second child's PID.
+ b := make([]byte, 8)
+ n, err := controlR.Read(b)
+ if err != nil {
+ t.Fatalf("error reading child pid: %v\n", err)
+ }
+ if n != 8 {
+ t.Fatalf("unexpected short read n = %d\n", n)
+ }
+ pid := binary.LittleEndian.Uint64(b[:])
+ process, err := os.FindProcess(int(pid))
+ if err != nil {
+ t.Fatalf("unable to find child process: %v", err)
+ }
+
+ // Wait for the third child to write a byte indicating that it is
+ // entering the read.
+ b = make([]byte, 1)
+ _, err = pty.Read(b)
+ if err != nil {
+ t.Fatalf("error reading from child: %v", err)
+ }
+
+ // Give the program time to enter the read call.
+ // It doesn't matter much if we occasionally don't wait long enough;
+ // we won't be testing what we want to test, but the overall test
+ // will pass.
+ time.Sleep(pause)
+
+ t.Logf("Sending ^Z...")
+
+ // Send a ^Z to stop the program.
+ if _, err := pty.Write([]byte{26}); err != nil {
+ t.Fatalf("writing ^Z to pty: %v", err)
+ }
+
+ // Wait for subprocess 1 to cycle the foreground process group.
+ if _, err := controlR.Read(b); err != nil {
+ t.Fatalf("error reading readiness: %v", err)
+ }
+
+ t.Logf("Sending SIGCONT...")
+
+ // Restart the stopped program.
+ if err := process.Signal(syscall.SIGCONT); err != nil {
+ t.Fatalf("Signal(SIGCONT) got err %v want nil", err)
+ }
+
+ // Write some data for the program to read, which should cause it to
+ // exit.
+ if _, err := pty.Write([]byte{'\n'}); err != nil {
+ t.Fatalf("writing %q to pty: %v", "\n", err)
+ }
+
+ t.Logf("Waiting for exit...")
+
+ if err = cmd.Wait(); err != nil {
+ t.Errorf("subprogram failed: %v", err)
+ }
+}
+
+// GO_TEST_TERMINAL_SIGNALS=1 subprocess above.
+func runSessionLeader(pause time.Duration) {
+ // "Attempts to use tcsetpgrp() from a process which is a
+ // member of a background process group on a fildes associated
+ // with its controlling terminal shall cause the process group
+ // to be sent a SIGTTOU signal. If the calling thread is
+ // blocking SIGTTOU signals or the process is ignoring SIGTTOU
+ // signals, the process shall be allowed to perform the
+ // operation, and no signal is sent."
+ // -https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcsetpgrp.html
+ //
+ // We are changing the terminal to put us in the foreground, so
+ // we must ignore SIGTTOU. We are also an orphaned process
+ // group (see above), so we must mask SIGTTOU to be eligible to
+ // become foreground at all.
+ signal.Ignore(syscall.SIGTTOU)
+
+ pty := os.NewFile(ptyFD, "pty")
+ controlW := os.NewFile(controlFD, "control-pipe")
+
+ // Slightly shorter timeout than in the parent.
+ ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
+ defer cancel()
+ cmd := exec.CommandContext(ctx, os.Args[0], "-test.run=TestTerminalSignal")
+ cmd.Env = append(os.Environ(), "GO_TEST_TERMINAL_SIGNALS=2")
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ cmd.ExtraFiles = []*os.File{pty}
+ cmd.SysProcAttr = &syscall.SysProcAttr{
+ Foreground: true,
+ Ctty: ptyFD,
+ }
+ if err := cmd.Start(); err != nil {
+ fmt.Fprintf(os.Stderr, "error starting second subprocess: %v\n", err)
+ os.Exit(1)
+ }
+
+ fn := func() error {
+ var b [8]byte
+ binary.LittleEndian.PutUint64(b[:], uint64(cmd.Process.Pid))
+ _, err := controlW.Write(b[:])
+ if err != nil {
+ return fmt.Errorf("error writing child pid: %w", err)
+ }
+
+ // Wait for stop.
+ var status syscall.WaitStatus
+ var errno syscall.Errno
+ for {
+ _, _, errno = syscall.Syscall6(syscall.SYS_WAIT4, uintptr(cmd.Process.Pid), uintptr(unsafe.Pointer(&status)), syscall.WUNTRACED, 0, 0, 0)
+ if errno != syscall.EINTR {
+ break
+ }
+ }
+ if errno != 0 {
+ return fmt.Errorf("error waiting for stop: %w", errno)
+ }
+
+ if !status.Stopped() {
+ return fmt.Errorf("unexpected wait status: %v", status)
+ }
+
+ // Take TTY.
+ pgrp := int32(syscall.Getpgrp()) // assume that pid_t is int32
+ _, _, errno = syscall.Syscall(syscall.SYS_IOCTL, ptyFD, syscall.TIOCSPGRP, uintptr(unsafe.Pointer(&pgrp)))
+ if errno != 0 {
+ return fmt.Errorf("error setting tty process group: %w", errno)
+ }
+
+ // Give the kernel time to potentially wake readers and have
+ // them return EINTR (darwin does this).
+ time.Sleep(pause)
+
+ // Give TTY back.
+ pid := int32(cmd.Process.Pid) // assume that pid_t is int32
+ _, _, errno = syscall.Syscall(syscall.SYS_IOCTL, ptyFD, syscall.TIOCSPGRP, uintptr(unsafe.Pointer(&pid)))
+ if errno != 0 {
+ return fmt.Errorf("error setting tty process group back: %w", errno)
+ }
+
+ // Report that we are done and SIGCONT can be sent. Note that
+ // the actual byte we send doesn't matter.
+ if _, err := controlW.Write(b[:1]); err != nil {
+ return fmt.Errorf("error writing readiness: %w", err)
+ }
+
+ return nil
+ }
+
+ err := fn()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "session leader error: %v\n", err)
+ cmd.Process.Kill()
+ // Wait for exit below.
+ }
+
+ werr := cmd.Wait()
+ if werr != nil {
+ fmt.Fprintf(os.Stderr, "error running second subprocess: %v\n", err)
+ }
+
+ if err != nil || werr != nil {
+ os.Exit(1)
+ }
+
+ os.Exit(0)
+}
+
+// GO_TEST_TERMINAL_SIGNALS=2 subprocess above.
+func runStoppingChild() {
+ pty := os.NewFile(ptyFD, "pty")
+
+ var b [1]byte
+ if _, err := pty.Write(b[:]); err != nil {
+ fmt.Fprintf(os.Stderr, "error writing byte to PTY: %v\n", err)
+ os.Exit(1)
+ }
+
+ _, err := pty.Read(b[:])
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+ if b[0] == '\n' {
+ // This is what we expect
+ fmt.Println("read newline")
+ } else {
+ fmt.Fprintf(os.Stderr, "read 1 unexpected byte: %q\n", b)
+ os.Exit(1)
+ }
+ os.Exit(0)
+}
diff --git a/src/os/signal/signal_linux_test.go b/src/os/signal/signal_linux_test.go
new file mode 100644
index 0000000..f70f108
--- /dev/null
+++ b/src/os/signal/signal_linux_test.go
@@ -0,0 +1,42 @@
+// Copyright 2020 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 linux
+
+package signal
+
+import (
+ "os"
+ "syscall"
+ "testing"
+ "time"
+)
+
+const prSetKeepCaps = 8
+
+// This test validates that syscall.AllThreadsSyscall() can reliably
+// reach all 'm' (threads) of the nocgo runtime even when one thread
+// is blocked waiting to receive signals from the kernel. This monitors
+// for a regression vs. the fix for #43149.
+func TestAllThreadsSyscallSignals(t *testing.T) {
+ if _, _, err := syscall.AllThreadsSyscall(syscall.SYS_PRCTL, prSetKeepCaps, 0, 0); err == syscall.ENOTSUP {
+ t.Skip("AllThreadsSyscall disabled with cgo")
+ }
+
+ sig := make(chan os.Signal, 1)
+ Notify(sig, os.Interrupt)
+
+ for i := 0; i <= 100; i++ {
+ if _, _, errno := syscall.AllThreadsSyscall(syscall.SYS_PRCTL, prSetKeepCaps, uintptr(i&1), 0); errno != 0 {
+ t.Fatalf("[%d] failed to set KEEP_CAPS=%d: %v", i, i&1, errno)
+ }
+ }
+
+ select {
+ case <-time.After(10 * time.Millisecond):
+ case <-sig:
+ t.Fatal("unexpected signal")
+ }
+ Stop(sig)
+}
diff --git a/src/os/signal/signal_plan9.go b/src/os/signal/signal_plan9.go
new file mode 100644
index 0000000..7d48715
--- /dev/null
+++ b/src/os/signal/signal_plan9.go
@@ -0,0 +1,64 @@
+// 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 signal
+
+import (
+ "os"
+ "syscall"
+)
+
+var sigtab = make(map[os.Signal]int)
+
+// Defined by the runtime package.
+func signal_disable(uint32)
+func signal_enable(uint32)
+func signal_ignore(uint32)
+func signal_ignored(uint32) bool
+func signal_recv() string
+
+func init() {
+ watchSignalLoop = loop
+}
+
+func loop() {
+ for {
+ process(syscall.Note(signal_recv()))
+ }
+}
+
+const numSig = 256
+
+func signum(sig os.Signal) int {
+ switch sig := sig.(type) {
+ case syscall.Note:
+ n, ok := sigtab[sig]
+ if !ok {
+ n = len(sigtab) + 1
+ if n > numSig {
+ return -1
+ }
+ sigtab[sig] = n
+ }
+ return n
+ default:
+ return -1
+ }
+}
+
+func enableSignal(sig int) {
+ signal_enable(uint32(sig))
+}
+
+func disableSignal(sig int) {
+ signal_disable(uint32(sig))
+}
+
+func ignoreSignal(sig int) {
+ signal_ignore(uint32(sig))
+}
+
+func signalIgnored(sig int) bool {
+ return signal_ignored(uint32(sig))
+}
diff --git a/src/os/signal/signal_plan9_test.go b/src/os/signal/signal_plan9_test.go
new file mode 100644
index 0000000..8357199
--- /dev/null
+++ b/src/os/signal/signal_plan9_test.go
@@ -0,0 +1,167 @@
+// 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.
+
+package signal
+
+import (
+ "internal/itoa"
+ "os"
+ "runtime"
+ "syscall"
+ "testing"
+ "time"
+)
+
+func waitSig(t *testing.T, c <-chan os.Signal, sig os.Signal) {
+ select {
+ case s := <-c:
+ if s != sig {
+ t.Fatalf("signal was %v, want %v", s, sig)
+ }
+ case <-time.After(1 * time.Second):
+ t.Fatalf("timeout waiting for %v", sig)
+ }
+}
+
+// Test that basic signal handling works.
+func TestSignal(t *testing.T) {
+ // Ask for hangup
+ c := make(chan os.Signal, 1)
+ Notify(c, syscall.Note("hangup"))
+ defer Stop(c)
+
+ // Send this process a hangup
+ t.Logf("hangup...")
+ postNote(syscall.Getpid(), "hangup")
+ waitSig(t, c, syscall.Note("hangup"))
+
+ // Ask for everything we can get.
+ c1 := make(chan os.Signal, 1)
+ Notify(c1)
+
+ // Send this process an alarm
+ t.Logf("alarm...")
+ postNote(syscall.Getpid(), "alarm")
+ waitSig(t, c1, syscall.Note("alarm"))
+
+ // Send two more hangups, to make sure that
+ // they get delivered on c1 and that not reading
+ // from c does not block everything.
+ t.Logf("hangup...")
+ postNote(syscall.Getpid(), "hangup")
+ waitSig(t, c1, syscall.Note("hangup"))
+ t.Logf("hangup...")
+ postNote(syscall.Getpid(), "hangup")
+ waitSig(t, c1, syscall.Note("hangup"))
+
+ // The first SIGHUP should be waiting for us on c.
+ waitSig(t, c, syscall.Note("hangup"))
+}
+
+func TestStress(t *testing.T) {
+ dur := 3 * time.Second
+ if testing.Short() {
+ dur = 100 * time.Millisecond
+ }
+ defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4))
+ done := make(chan bool)
+ finished := make(chan bool)
+ go func() {
+ sig := make(chan os.Signal, 1)
+ Notify(sig, syscall.Note("alarm"))
+ defer Stop(sig)
+ Loop:
+ for {
+ select {
+ case <-sig:
+ case <-done:
+ break Loop
+ }
+ }
+ finished <- true
+ }()
+ go func() {
+ Loop:
+ for {
+ select {
+ case <-done:
+ break Loop
+ default:
+ postNote(syscall.Getpid(), "alarm")
+ runtime.Gosched()
+ }
+ }
+ finished <- true
+ }()
+ time.Sleep(dur)
+ close(done)
+ <-finished
+ <-finished
+ // When run with 'go test -cpu=1,2,4' alarm from this test can slip
+ // into subsequent TestSignal() causing failure.
+ // Sleep for a while to reduce the possibility of the failure.
+ time.Sleep(10 * time.Millisecond)
+}
+
+// Test that Stop cancels the channel's registrations.
+func TestStop(t *testing.T) {
+ if testing.Short() {
+ t.Skip("skipping in short mode")
+ }
+ sigs := []string{
+ "alarm",
+ "hangup",
+ }
+
+ for _, sig := range sigs {
+ // Send the signal.
+ // If it's alarm, we should not see it.
+ // If it's hangup, maybe we'll die. Let the flag tell us what to do.
+ if sig != "hangup" {
+ postNote(syscall.Getpid(), sig)
+ }
+ time.Sleep(100 * time.Millisecond)
+
+ // Ask for signal
+ c := make(chan os.Signal, 1)
+ Notify(c, syscall.Note(sig))
+ defer Stop(c)
+
+ // Send this process that signal
+ postNote(syscall.Getpid(), sig)
+ waitSig(t, c, syscall.Note(sig))
+
+ Stop(c)
+ select {
+ case s := <-c:
+ t.Fatalf("unexpected signal %v", s)
+ case <-time.After(100 * time.Millisecond):
+ // nothing to read - good
+ }
+
+ // Send the signal.
+ // If it's alarm, we should not see it.
+ // If it's hangup, maybe we'll die. Let the flag tell us what to do.
+ if sig != "hangup" {
+ postNote(syscall.Getpid(), sig)
+ }
+
+ select {
+ case s := <-c:
+ t.Fatalf("unexpected signal %v", s)
+ case <-time.After(100 * time.Millisecond):
+ // nothing to read - good
+ }
+ }
+}
+
+func postNote(pid int, note string) error {
+ f, err := os.OpenFile("/proc/"+itoa.Itoa(pid)+"/note", os.O_WRONLY, 0)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+ _, err = f.Write([]byte(note))
+ return err
+}
diff --git a/src/os/signal/signal_test.go b/src/os/signal/signal_test.go
new file mode 100644
index 0000000..c7c42ed
--- /dev/null
+++ b/src/os/signal/signal_test.go
@@ -0,0 +1,932 @@
+// 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 := testenv.Command(t, 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 := testenv.Command(t, "/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 {
+ // nohup doesn't work on new LUCI darwin builders due to the
+ // type of launchd service the test run under. See
+ // https://go.dev/issue/63875.
+ if runtime.GOOS == "darwin" && strings.Contains(string(out), "nohup: can't detach from console: Inappropriate ioctl for device") {
+ t.Skip("Skipping nohup test due to darwin builder limitation. See https://go.dev/issue/63875.")
+ }
+
+ 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) {
+ // 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.
+
+ t.Run("uncaught", func(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)
+ t.Cleanup(func() { Stop(c) })
+
+ var subTimeout time.Duration
+ 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
+ t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
+ t.Parallel()
+
+ 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 := testenv.Command(t, 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)
+ }
+ })
+ }
+ })
+
+ t.Run("nohup", func(t *testing.T) {
+ // 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")
+ }
+
+ var subTimeout time.Duration
+ 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
+ t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
+ t.Parallel()
+
+ // POSIX specifies that nohup writes to a file named nohup.out if standard
+ // output is a terminal. However, for an exec.Cmd, 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 := testenv.Command(t, "nohup", args...).CombinedOutput()
+
+ if err != nil {
+ // nohup doesn't work on new LUCI darwin builders due to the
+ // type of launchd service the test run under. See
+ // https://go.dev/issue/63875.
+ if runtime.GOOS == "darwin" && strings.Contains(string(out), "nohup: can't detach from console: Inappropriate ioctl for device") {
+ // TODO(go.dev/issue/63799): A false-positive in vet reports a
+ // t.Skip here as invalid. Switch back to t.Skip once fixed.
+ t.Logf("Skipping nohup test due to darwin builder limitation. See https://go.dev/issue/63875.")
+ return
+ }
+
+ 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)
+ }
+ })
+ }
+ })
+}
+
+// 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 := testenv.Command(t, 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 := testenv.Command(t, 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
+}
diff --git a/src/os/signal/signal_unix.go b/src/os/signal/signal_unix.go
new file mode 100644
index 0000000..21dfa41
--- /dev/null
+++ b/src/os/signal/signal_unix.go
@@ -0,0 +1,62 @@
+// 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.
+
+//go:build unix || (js && wasm) || wasip1 || windows
+
+package signal
+
+import (
+ "os"
+ "syscall"
+)
+
+// Defined by the runtime package.
+func signal_disable(uint32)
+func signal_enable(uint32)
+func signal_ignore(uint32)
+func signal_ignored(uint32) bool
+func signal_recv() uint32
+
+func loop() {
+ for {
+ process(syscall.Signal(signal_recv()))
+ }
+}
+
+func init() {
+ watchSignalLoop = loop
+}
+
+const (
+ numSig = 65 // max across all systems
+)
+
+func signum(sig os.Signal) int {
+ switch sig := sig.(type) {
+ case syscall.Signal:
+ i := int(sig)
+ if i < 0 || i >= numSig {
+ return -1
+ }
+ return i
+ default:
+ return -1
+ }
+}
+
+func enableSignal(sig int) {
+ signal_enable(uint32(sig))
+}
+
+func disableSignal(sig int) {
+ signal_disable(uint32(sig))
+}
+
+func ignoreSignal(sig int) {
+ signal_ignore(uint32(sig))
+}
+
+func signalIgnored(sig int) bool {
+ return signal_ignored(uint32(sig))
+}
diff --git a/src/os/signal/signal_windows_test.go b/src/os/signal/signal_windows_test.go
new file mode 100644
index 0000000..145a805
--- /dev/null
+++ b/src/os/signal/signal_windows_test.go
@@ -0,0 +1,98 @@
+// 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 signal
+
+import (
+ "internal/testenv"
+ "os"
+ "path/filepath"
+ "strings"
+ "syscall"
+ "testing"
+ "time"
+)
+
+func sendCtrlBreak(t *testing.T, pid int) {
+ d, e := syscall.LoadDLL("kernel32.dll")
+ if e != nil {
+ t.Fatalf("LoadDLL: %v\n", e)
+ }
+ p, e := d.FindProc("GenerateConsoleCtrlEvent")
+ if e != nil {
+ t.Fatalf("FindProc: %v\n", e)
+ }
+ r, _, e := p.Call(syscall.CTRL_BREAK_EVENT, uintptr(pid))
+ if r == 0 {
+ t.Fatalf("GenerateConsoleCtrlEvent: %v\n", e)
+ }
+}
+
+func TestCtrlBreak(t *testing.T) {
+ // create source file
+ const source = `
+package main
+
+import (
+ "log"
+ "os"
+ "os/signal"
+ "time"
+)
+
+
+func main() {
+ c := make(chan os.Signal, 10)
+ signal.Notify(c)
+ select {
+ case s := <-c:
+ if s != os.Interrupt {
+ log.Fatalf("Wrong signal received: got %q, want %q\n", s, os.Interrupt)
+ }
+ case <-time.After(3 * time.Second):
+ log.Fatalf("Timeout waiting for Ctrl+Break\n")
+ }
+}
+`
+ tmp := t.TempDir()
+
+ // write ctrlbreak.go
+ name := filepath.Join(tmp, "ctlbreak")
+ src := name + ".go"
+ f, err := os.Create(src)
+ if err != nil {
+ t.Fatalf("Failed to create %v: %v", src, err)
+ }
+ defer f.Close()
+ f.Write([]byte(source))
+
+ // compile it
+ exe := name + ".exe"
+ defer os.Remove(exe)
+ o, err := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", exe, src).CombinedOutput()
+ if err != nil {
+ t.Fatalf("Failed to compile: %v\n%v", err, string(o))
+ }
+
+ // run it
+ cmd := testenv.Command(t, exe)
+ var buf strings.Builder
+ cmd.Stdout = &buf
+ cmd.Stderr = &buf
+ cmd.SysProcAttr = &syscall.SysProcAttr{
+ CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP,
+ }
+ err = cmd.Start()
+ if err != nil {
+ t.Fatalf("Start failed: %v", err)
+ }
+ go func() {
+ time.Sleep(1 * time.Second)
+ sendCtrlBreak(t, cmd.Process.Pid)
+ }()
+ err = cmd.Wait()
+ if err != nil {
+ t.Fatalf("Program exited with error: %v\n%v", err, buf.String())
+ }
+}