diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 13:14:23 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 13:14:23 +0000 |
commit | 73df946d56c74384511a194dd01dbe099584fd1a (patch) | |
tree | fd0bcea490dd81327ddfbb31e215439672c9a068 /src/os/signal | |
parent | Initial commit. (diff) | |
download | golang-1.16-upstream.tar.xz golang-1.16-upstream.zip |
Adding upstream version 1.16.10.upstream/1.16.10upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/os/signal')
-rw-r--r-- | src/os/signal/doc.go | 229 | ||||
-rw-r--r-- | src/os/signal/example_test.go | 38 | ||||
-rw-r--r-- | src/os/signal/example_unix_test.go | 47 | ||||
-rw-r--r-- | src/os/signal/internal/pty/pty.go | 59 | ||||
-rw-r--r-- | src/os/signal/sig.s | 8 | ||||
-rw-r--r-- | src/os/signal/signal.go | 334 | ||||
-rw-r--r-- | src/os/signal/signal_cgo_test.go | 240 | ||||
-rw-r--r-- | src/os/signal/signal_linux_test.go | 42 | ||||
-rw-r--r-- | src/os/signal/signal_plan9.go | 64 | ||||
-rw-r--r-- | src/os/signal/signal_plan9_test.go | 181 | ||||
-rw-r--r-- | src/os/signal/signal_test.go | 897 | ||||
-rw-r--r-- | src/os/signal/signal_unix.go | 62 | ||||
-rw-r--r-- | src/os/signal/signal_windows_test.go | 103 |
13 files changed, 2304 insertions, 0 deletions
diff --git a/src/os/signal/doc.go b/src/os/signal/doc.go new file mode 100644 index 0000000..2229d36 --- /dev/null +++ b/src/os/signal/doc.go @@ -0,0 +1,229 @@ +// 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 GNU/Linux, signals 32 (SIGCANCEL) and 33 (SIGSETXID) +(SIGCANCEL and SIGSETXID are used internally by glibc). Subprocesses +started by os.Exec, or by the os/exec package, 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. Also, the Go standard library expects that any signal handlers +will use the SA_RESTART flag. Failing to do so may cause some library +calls to return "interrupted system call" errors. + +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. + +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 GNU/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..a0af37a --- /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. + +// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris + +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/internal/pty/pty.go b/src/os/signal/internal/pty/pty.go new file mode 100644 index 0000000..f8813ce --- /dev/null +++ b/src/os/signal/internal/pty/pty.go @@ -0,0 +1,59 @@ +// 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. + +// +build aix darwin dragonfly freebsd linux,!android netbsd openbsd +// +build cgo + +// Package pty is a simple pseudo-terminal package for Unix systems, +// implemented by calling C functions via cgo. +// This is only used for testing the os/signal package. +package pty + +/* +#define _XOPEN_SOURCE 600 +#include <fcntl.h> +#include <stdlib.h> +#include <unistd.h> +*/ +import "C" + +import ( + "fmt" + "os" + "syscall" +) + +type PtyError struct { + FuncName string + ErrorString string + Errno syscall.Errno +} + +func ptyError(name string, err error) *PtyError { + return &PtyError{name, err.Error(), err.(syscall.Errno)} +} + +func (e *PtyError) Error() string { + return fmt.Sprintf("%s: %s", e.FuncName, e.ErrorString) +} + +func (e *PtyError) Unwrap() error { return e.Errno } + +// Open returns a control pty and the name of the linked process tty. +func Open() (pty *os.File, processTTY string, err error) { + m, err := C.posix_openpt(C.O_RDWR) + if err != nil { + return nil, "", ptyError("posix_openpt", err) + } + if _, err := C.grantpt(m); err != nil { + C.close(m) + return nil, "", ptyError("grantpt", err) + } + if _, err := C.unlockpt(m); err != nil { + C.close(m) + return nil, "", ptyError("unlockpt", err) + } + processTTY = C.GoString(C.ptsname(m)) + return os.NewFile(uintptr(m), "pty"), processTTY, nil +} 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..a8a4856 --- /dev/null +++ b/src/os/signal/signal_cgo_test.go @@ -0,0 +1,240 @@ +// 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. + +// +build darwin dragonfly freebsd linux,!android netbsd openbsd +// +build 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 ( + "bufio" + "bytes" + "context" + "fmt" + "io" + "io/fs" + "os" + "os/exec" + ptypkg "os/signal/internal/pty" + "strconv" + "strings" + "sync" + "syscall" + "testing" + "time" +) + +func TestTerminalSignal(t *testing.T) { + const enteringRead = "test program entering read" + if os.Getenv("GO_TEST_TERMINAL_SIGNALS") != "" { + var b [1]byte + fmt.Println(enteringRead) + n, err := os.Stdin.Read(b[:]) + if n == 1 { + if b[0] == '\n' { + // This is what we expect + fmt.Println("read newline") + } else { + fmt.Printf("read 1 byte: %q\n", b) + } + } else { + fmt.Printf("read %d bytes\n", n) + } + if err != nil { + fmt.Println(err) + os.Exit(1) + } + os.Exit(0) + } + + t.Parallel() + + // The test requires a shell that uses job control. + bash, err := exec.LookPath("bash") + if err != nil { + t.Skipf("could not find bash: %v", err) + } + + 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 + wait := time.Duration(scale) * 5 * time.Second + + // The test only fails when using a "slow device," in this + // case a pseudo-terminal. + + pty, procTTYName, err := ptypkg.Open() + if err != nil { + ptyErr := err.(*ptypkg.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() + + // Start an interactive shell. + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + cmd := exec.CommandContext(ctx, bash, "--norc", "--noprofile", "-i") + // Clear HISTFILE so that we don't read or clobber the user's bash history. + cmd.Env = append(os.Environ(), "HISTFILE=") + cmd.Stdin = procTTY + cmd.Stdout = procTTY + cmd.Stderr = procTTY + cmd.SysProcAttr = &syscall.SysProcAttr{ + Setsid: true, + Setctty: true, + Ctty: 0, + } + + if err := cmd.Start(); err != nil { + t.Fatal(err) + } + + if err := procTTY.Close(); err != nil { + t.Errorf("closing procTTY: %v", err) + } + + progReady := make(chan bool) + sawPrompt := make(chan bool, 10) + const prompt = "prompt> " + + // Read data from pty in the background. + var wg sync.WaitGroup + wg.Add(1) + defer wg.Wait() + go func() { + defer wg.Done() + input := bufio.NewReader(pty) + var line, handled []byte + for { + b, err := input.ReadByte() + if err != nil { + if len(line) > 0 || len(handled) > 0 { + t.Logf("%q", append(handled, line...)) + } + if perr, ok := err.(*fs.PathError); ok { + err = perr.Err + } + // EOF means pty is closed. + // EIO means child process is done. + // "file already closed" means deferred close of pty has happened. + if err != io.EOF && err != syscall.EIO && !strings.Contains(err.Error(), "file already closed") { + t.Logf("error reading from pty: %v", err) + } + return + } + + line = append(line, b) + + if b == '\n' { + t.Logf("%q", append(handled, line...)) + line = nil + handled = nil + continue + } + + if bytes.Contains(line, []byte(enteringRead)) { + close(progReady) + handled = append(handled, line...) + line = nil + } else if bytes.Contains(line, []byte(prompt)) && !bytes.Contains(line, []byte("PS1=")) { + sawPrompt <- true + handled = append(handled, line...) + line = nil + } + } + }() + + // Set the bash prompt so that we can see it. + if _, err := pty.Write([]byte("PS1='" + prompt + "'\n")); err != nil { + t.Fatalf("setting prompt: %v", err) + } + select { + case <-sawPrompt: + case <-time.After(wait): + t.Fatal("timed out waiting for shell prompt") + } + + // Start a small program that reads from stdin + // (namely the code at the top of this function). + if _, err := pty.Write([]byte("GO_TEST_TERMINAL_SIGNALS=1 " + os.Args[0] + " -test.run=TestTerminalSignal\n")); err != nil { + t.Fatal(err) + } + + // Wait for the program to print that it is starting. + select { + case <-progReady: + case <-time.After(wait): + t.Fatal("timed out waiting for program to start") + } + + // 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) + + // 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 the program to stop and return to the shell. + select { + case <-sawPrompt: + case <-time.After(wait): + t.Fatal("timed out waiting for shell prompt") + } + + // Restart the stopped program. + if _, err := pty.Write([]byte("fg\n")); err != nil { + t.Fatalf("writing %q to pty: %v", "fg", err) + } + + // Give the process time to restart. + // This is potentially racy: if the process does not restart + // quickly enough then the byte we send will go to bash rather + // than the program. Unfortunately there isn't anything we can + // look for to know that the program is running again. + // bash will print the program name, but that happens before it + // restarts the program. + time.Sleep(10 * pause) + + // 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) + } + + // Wait for the program to exit. + select { + case <-sawPrompt: + case <-time.After(wait): + t.Fatal("timed out waiting for shell prompt") + } + + // Exit the shell with the program's exit status. + if _, err := pty.Write([]byte("exit $?\n")); err != nil { + t.Fatalf("writing %q to pty: %v", "exit", err) + } + + if err = cmd.Wait(); err != nil { + t.Errorf("subprogram failed: %v", err) + } +} diff --git a/src/os/signal/signal_linux_test.go b/src/os/signal/signal_linux_test.go new file mode 100644 index 0000000..2e553d0 --- /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. + +// +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..10bfdc3 --- /dev/null +++ b/src/os/signal/signal_plan9_test.go @@ -0,0 +1,181 @@ +// 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 ( + "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 itoa(val int) string { + if val < 0 { + return "-" + itoa(-val) + } + var buf [32]byte // big enough for int64 + i := len(buf) - 1 + for val >= 10 { + buf[i] = byte(val%10 + '0') + i-- + val /= 10 + } + buf[i] = byte(val + '0') + return string(buf[i:]) +} + +func postNote(pid int, note string) error { + f, err := os.OpenFile("/proc/"+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..9e5ee8b --- /dev/null +++ b/src/os/signal/signal_test.go @@ -0,0 +1,897 @@ +// 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. + +// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris + +package signal + +import ( + "bytes" + "context" + "flag" + "fmt" + "internal/testenv" + "os" + "os/exec" + "runtime" + "runtime/trace" + "strconv" + "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 + +func init() { + if testenv.Builder() == "solaris-amd64-oraclerel" { + // The solaris-amd64-oraclerel builder has been observed to time out in + // TestNohup even with a 250ms settle time. + // + // Use a much longer settle time on that builder to try to suss out whether + // the test is flaky due to builder slowness (which may mean we need a + // longer GO_TEST_TIMEOUT_SCALE) or due to a dropped signal (which may + // instead need a test-skip and upstream bug filed against the Solaris + // kernel). + // + // This constant is chosen so as to make the test as generous as possible + // while still reliably completing within 3 minutes in non-short mode. + // + // See https://golang.org/issue/33174. + settleTime = 11 * time.Second + } else if 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) < settleTime { + 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", settleTime, 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) + + // Send this process a SIGWINCH + t.Logf("sigwinch...") + syscall.Kill(syscall.Getpid(), syscall.SIGWINCH) + waitSigAll(t, c1, syscall.SIGWINCH) + + // Send two more SIGHUPs, to make sure that + // they get delivered on c1 and that not reading + // from c does not block everything. + t.Logf("sighup...") + syscall.Kill(syscall.Getpid(), syscall.SIGHUP) + waitSigAll(t, c1, syscall.SIGHUP) + t.Logf("sighup...") + syscall.Kill(syscall.Getpid(), syscall.SIGHUP) + waitSigAll(t, c1, syscall.SIGHUP) + + // The first SIGHUP should be waiting for us on c. + waitSig(t, c, syscall.SIGHUP) +} + +func TestStress(t *testing.T) { + dur := 3 * time.Second + if testing.Short() { + dur = 100 * time.Millisecond + } + defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4)) + + sig := make(chan os.Signal, 1) + Notify(sig, syscall.SIGUSR1) + + go func() { + stop := time.After(dur) + for { + select { + case <-stop: + // Allow enough time for all signals to be delivered before we stop + // listening for them. + quiesce() + Stop(sig) + // According to its documentation, “[w]hen Stop returns, it in + // guaranteed that c will receive no more signals.” So we can safely + // close sig here: if there is a send-after-close race here, that is a + // bug in Stop and we would like to detect it. + close(sig) + return + + default: + syscall.Kill(syscall.Getpid(), syscall.SIGUSR1) + runtime.Gosched() + } + } + }() + + for range sig { + // Receive signals until the sender closes sig. + } +} + +func testCancel(t *testing.T, ignore bool) { + // Ask to be notified on c1 when a SIGWINCH is received. + c1 := make(chan os.Signal, 1) + Notify(c1, syscall.SIGWINCH) + defer Stop(c1) + + // Ask to be notified on c2 when a SIGHUP is received. + c2 := make(chan os.Signal, 1) + Notify(c2, syscall.SIGHUP) + defer Stop(c2) + + // Send this process a SIGWINCH and wait for notification on c1. + syscall.Kill(syscall.Getpid(), syscall.SIGWINCH) + waitSig(t, c1, syscall.SIGWINCH) + + // Send this process a SIGHUP and wait for notification on c2. + syscall.Kill(syscall.Getpid(), syscall.SIGHUP) + waitSig(t, c2, syscall.SIGHUP) + + // Ignore, or reset the signal handlers for, SIGWINCH and SIGHUP. + // Either way, this should undo both calls to Notify above. + if ignore { + Ignore(syscall.SIGWINCH, syscall.SIGHUP) + // Don't bother deferring a call to Reset: it is documented to undo Notify, + // but its documentation says nothing about Ignore, and (as of the time of + // writing) it empirically does not undo an Ignore. + } else { + Reset(syscall.SIGWINCH, syscall.SIGHUP) + } + + // Send this process a SIGWINCH. It should be ignored. + syscall.Kill(syscall.Getpid(), syscall.SIGWINCH) + + // If ignoring, Send this process a SIGHUP. It should be ignored. + if ignore { + syscall.Kill(syscall.Getpid(), syscall.SIGHUP) + } + + quiesce() + + select { + case s := <-c1: + t.Errorf("unexpected signal %v", s) + default: + // nothing to read - good + } + + select { + case s := <-c2: + t.Errorf("unexpected signal %v", s) + default: + // nothing to read - good + } + + // One or both of the signals may have been blocked for this process + // by the calling process. + // Discard any queued signals now to avoid interfering with other tests. + Notify(c1, syscall.SIGWINCH) + Notify(c2, syscall.SIGHUP) + quiesce() +} + +// Test that Reset cancels registration for listed signals on all channels. +func TestReset(t *testing.T) { + testCancel(t, false) +} + +// Test that Ignore cancels registration for listed signals on all channels. +func TestIgnore(t *testing.T) { + testCancel(t, true) +} + +// Test that Ignored correctly detects changes to the ignored status of a signal. +func TestIgnored(t *testing.T) { + // Ask to be notified on SIGWINCH. + c := make(chan os.Signal, 1) + Notify(c, syscall.SIGWINCH) + + // If we're being notified, then the signal should not be ignored. + if Ignored(syscall.SIGWINCH) { + t.Errorf("expected SIGWINCH to not be ignored.") + } + Stop(c) + Ignore(syscall.SIGWINCH) + + // We're no longer paying attention to this signal. + if !Ignored(syscall.SIGWINCH) { + t.Errorf("expected SIGWINCH to be ignored when explicitly ignoring it.") + } + + Reset() +} + +var checkSighupIgnored = flag.Bool("check_sighup_ignored", false, "if true, TestDetectNohup will fail if SIGHUP is not ignored.") + +// Test that Ignored(SIGHUP) correctly detects whether it is being run under nohup. +func TestDetectNohup(t *testing.T) { + if *checkSighupIgnored { + if !Ignored(syscall.SIGHUP) { + t.Fatal("SIGHUP is not ignored.") + } else { + t.Log("SIGHUP is ignored.") + } + } else { + defer Reset() + // Ugly: ask for SIGHUP so that child will not have no-hup set + // even if test is running under nohup environment. + // We have no intention of reading from c. + c := make(chan os.Signal, 1) + Notify(c, syscall.SIGHUP) + if out, err := exec.Command(os.Args[0], "-test.run=TestDetectNohup", "-check_sighup_ignored").CombinedOutput(); err == nil { + t.Errorf("ran test with -check_sighup_ignored and it succeeded: expected failure.\nOutput:\n%s", out) + } + Stop(c) + // Again, this time with nohup, assuming we can find it. + _, err := os.Stat("/usr/bin/nohup") + if err != nil { + t.Skip("cannot find nohup; skipping second half of test") + } + Ignore(syscall.SIGHUP) + os.Remove("nohup.out") + out, err := exec.Command("/usr/bin/nohup", os.Args[0], "-test.run=TestDetectNohup", "-check_sighup_ignored").CombinedOutput() + + data, _ := os.ReadFile("nohup.out") + os.Remove("nohup.out") + if err != nil { + t.Errorf("ran test with -check_sighup_ignored under nohup and it failed: expected success.\nError: %v\nOutput:\n%s%s", err, out, data) + } + } +} + +var ( + sendUncaughtSighup = flag.Int("send_uncaught_sighup", 0, "send uncaught SIGHUP during TestStop") + dieFromSighup = flag.Bool("die_from_sighup", false, "wait to die from uncaught SIGHUP") +) + +// Test that Stop cancels the channel's registrations. +func TestStop(t *testing.T) { + sigs := []syscall.Signal{ + syscall.SIGWINCH, + syscall.SIGHUP, + syscall.SIGUSR1, + } + + for _, sig := range sigs { + sig := sig + t.Run(fmt.Sprint(sig), func(t *testing.T) { + // When calling Notify with a specific signal, + // independent signals should not interfere with each other, + // and we end up needing to wait for signals to quiesce a lot. + // Test the three different signals concurrently. + t.Parallel() + + // If the signal is not ignored, send the signal before registering a + // channel to verify the behavior of the default Go handler. + // If it's SIGWINCH or SIGUSR1 we should not see it. + // If it's SIGHUP, maybe we'll die. Let the flag tell us what to do. + mayHaveBlockedSignal := false + if !Ignored(sig) && (sig != syscall.SIGHUP || *sendUncaughtSighup == 1) { + syscall.Kill(syscall.Getpid(), sig) + quiesce() + + // We don't know whether sig is blocked for this process; see + // https://golang.org/issue/38165. Assume that it could be. + mayHaveBlockedSignal = true + } + + // Ask for signal + c := make(chan os.Signal, 1) + Notify(c, sig) + + // Send this process the signal again. + syscall.Kill(syscall.Getpid(), sig) + waitSig(t, c, sig) + + if mayHaveBlockedSignal { + // We may have received a queued initial signal in addition to the one + // that we sent after Notify. If so, waitSig may have observed that + // initial signal instead of the second one, and we may need to wait for + // the second signal to clear. Do that now. + quiesce() + select { + case <-c: + default: + } + } + + // Stop watching for the signal and send it again. + // If it's SIGHUP, maybe we'll die. Let the flag tell us what to do. + Stop(c) + if sig != syscall.SIGHUP || *sendUncaughtSighup == 2 { + syscall.Kill(syscall.Getpid(), sig) + quiesce() + + select { + case s := <-c: + t.Errorf("unexpected signal %v", s) + default: + // nothing to read - good + } + + // If we're going to receive a signal, it has almost certainly been + // received by now. However, it may have been blocked for this process — + // we don't know. Explicitly unblock it and wait for it to clear now. + Notify(c, sig) + quiesce() + Stop(c) + } + }) + } +} + +// Test that when run under nohup, an uncaught SIGHUP does not kill the program. +func TestNohup(t *testing.T) { + // Ugly: ask for SIGHUP so that child will not have no-hup set + // even if test is running under nohup environment. + // We have no intention of reading from c. + c := make(chan os.Signal, 1) + Notify(c, syscall.SIGHUP) + + // When run without nohup, the test should crash on an uncaught SIGHUP. + // When run under nohup, the test should ignore uncaught SIGHUPs, + // because the runtime is not supposed to be listening for them. + // Either way, TestStop should still be able to catch them when it wants them + // and then when it stops wanting them, the original behavior should resume. + // + // send_uncaught_sighup=1 sends the SIGHUP before starting to listen for SIGHUPs. + // send_uncaught_sighup=2 sends the SIGHUP after no longer listening for SIGHUPs. + // + // Both should fail without nohup and succeed with nohup. + + var subTimeout time.Duration + + var wg sync.WaitGroup + wg.Add(2) + if deadline, ok := t.Deadline(); ok { + subTimeout = time.Until(deadline) + subTimeout -= subTimeout / 10 // Leave 10% headroom for propagating output. + } + for i := 1; i <= 2; i++ { + i := i + go t.Run(fmt.Sprintf("uncaught-%d", i), func(t *testing.T) { + defer wg.Done() + + args := []string{ + "-test.v", + "-test.run=TestStop", + "-send_uncaught_sighup=" + strconv.Itoa(i), + "-die_from_sighup", + } + if subTimeout != 0 { + args = append(args, fmt.Sprintf("-test.timeout=%v", subTimeout)) + } + out, err := exec.Command(os.Args[0], args...).CombinedOutput() + + if err == nil { + t.Errorf("ran test with -send_uncaught_sighup=%d and it succeeded: expected failure.\nOutput:\n%s", i, out) + } else { + t.Logf("test with -send_uncaught_sighup=%d failed as expected.\nError: %v\nOutput:\n%s", i, err, out) + } + }) + } + wg.Wait() + + Stop(c) + + // Skip the nohup test below when running in tmux on darwin, since nohup + // doesn't work correctly there. See issue #5135. + if runtime.GOOS == "darwin" && os.Getenv("TMUX") != "" { + t.Skip("Skipping nohup test due to running in tmux on darwin") + } + + // Again, this time with nohup, assuming we can find it. + _, err := exec.LookPath("nohup") + if err != nil { + t.Skip("cannot find nohup; skipping second half of test") + } + + wg.Add(2) + if deadline, ok := t.Deadline(); ok { + subTimeout = time.Until(deadline) + subTimeout -= subTimeout / 10 // Leave 10% headroom for propagating output. + } + for i := 1; i <= 2; i++ { + i := i + go t.Run(fmt.Sprintf("nohup-%d", i), func(t *testing.T) { + defer wg.Done() + + // POSIX specifies that nohup writes to a file named nohup.out if standard + // output is a terminal. However, for an exec.Command, standard output is + // not a terminal — so we don't need to read or remove that file (and, + // indeed, cannot even create it if the current user is unable to write to + // GOROOT/src, such as when GOROOT is installed and owned by root). + + args := []string{ + os.Args[0], + "-test.v", + "-test.run=TestStop", + "-send_uncaught_sighup=" + strconv.Itoa(i), + } + if subTimeout != 0 { + args = append(args, fmt.Sprintf("-test.timeout=%v", subTimeout)) + } + out, err := exec.Command("nohup", args...).CombinedOutput() + + if err != nil { + t.Errorf("ran test with -send_uncaught_sighup=%d under nohup and it failed: expected success.\nError: %v\nOutput:\n%s", i, err, out) + } else { + t.Logf("ran test with -send_uncaught_sighup=%d under nohup.\nOutput:\n%s", i, out) + } + }) + } + wg.Wait() +} + +// Test that SIGCONT works (issue 8953). +func TestSIGCONT(t *testing.T) { + c := make(chan os.Signal, 1) + Notify(c, syscall.SIGCONT) + defer Stop(c) + syscall.Kill(syscall.Getpid(), syscall.SIGCONT) + waitSig(t, c, syscall.SIGCONT) +} + +// Test race between stopping and receiving a signal (issue 14571). +func TestAtomicStop(t *testing.T) { + if os.Getenv("GO_TEST_ATOMIC_STOP") != "" { + atomicStopTestProgram(t) + t.Fatal("atomicStopTestProgram returned") + } + + testenv.MustHaveExec(t) + + // Call Notify for SIGINT before starting the child process. + // That ensures that SIGINT is not ignored for the child. + // This is necessary because if SIGINT is ignored when a + // Go program starts, then it remains ignored, and closing + // the last notification channel for SIGINT will switch it + // back to being ignored. In that case the assumption of + // atomicStopTestProgram, that it will either die from SIGINT + // or have it be reported, breaks down, as there is a third + // option: SIGINT might be ignored. + cs := make(chan os.Signal, 1) + Notify(cs, syscall.SIGINT) + defer Stop(cs) + + const execs = 10 + for i := 0; i < execs; i++ { + timeout := "0" + if deadline, ok := t.Deadline(); ok { + timeout = time.Until(deadline).String() + } + cmd := exec.Command(os.Args[0], "-test.run=TestAtomicStop", "-test.timeout="+timeout) + cmd.Env = append(os.Environ(), "GO_TEST_ATOMIC_STOP=1") + out, err := cmd.CombinedOutput() + if err == nil { + if len(out) > 0 { + t.Logf("iteration %d: output %s", i, out) + } + } else { + t.Logf("iteration %d: exit status %q: output: %s", i, err, out) + } + + lost := bytes.Contains(out, []byte("lost signal")) + if lost { + t.Errorf("iteration %d: lost signal", i) + } + + // The program should either die due to SIGINT, + // or exit with success without printing "lost signal". + if err == nil { + if len(out) > 0 && !lost { + t.Errorf("iteration %d: unexpected output", i) + } + } else { + if ee, ok := err.(*exec.ExitError); !ok { + t.Errorf("iteration %d: error (%v) has type %T; expected exec.ExitError", i, err, err) + } else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok { + t.Errorf("iteration %d: error.Sys (%v) has type %T; expected syscall.WaitStatus", i, ee.Sys(), ee.Sys()) + } else if !ws.Signaled() || ws.Signal() != syscall.SIGINT { + t.Errorf("iteration %d: got exit status %v; expected SIGINT", i, ee) + } + } + } +} + +// atomicStopTestProgram is run in a subprocess by TestAtomicStop. +// It tries to trigger a signal delivery race. This function should +// either catch a signal or die from it. +func atomicStopTestProgram(t *testing.T) { + // This test won't work if SIGINT is ignored here. + if Ignored(syscall.SIGINT) { + fmt.Println("SIGINT is ignored") + os.Exit(1) + } + + const tries = 10 + + timeout := 2 * time.Second + if deadline, ok := t.Deadline(); ok { + // Give each try an equal slice of the deadline, with one slice to spare for + // cleanup. + timeout = time.Until(deadline) / (tries + 1) + } + + pid := syscall.Getpid() + printed := false + for i := 0; i < tries; i++ { + cs := make(chan os.Signal, 1) + Notify(cs, syscall.SIGINT) + + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + Stop(cs) + }() + + syscall.Kill(pid, syscall.SIGINT) + + // At this point we should either die from SIGINT or + // get a notification on cs. If neither happens, we + // dropped the signal. It is given 2 seconds to + // deliver, as needed for gccgo on some loaded test systems. + + select { + case <-cs: + case <-time.After(timeout): + if !printed { + fmt.Print("lost signal on tries:") + printed = true + } + fmt.Printf(" %d", i) + } + + wg.Wait() + } + if printed { + fmt.Print("\n") + } + + os.Exit(0) +} + +func TestTime(t *testing.T) { + // Test that signal works fine when we are in a call to get time, + // which on some platforms is using VDSO. See issue #34391. + dur := 3 * time.Second + if testing.Short() { + dur = 100 * time.Millisecond + } + defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4)) + + sig := make(chan os.Signal, 1) + Notify(sig, syscall.SIGUSR1) + + stop := make(chan struct{}) + go func() { + for { + select { + case <-stop: + // Allow enough time for all signals to be delivered before we stop + // listening for them. + quiesce() + Stop(sig) + // According to its documentation, “[w]hen Stop returns, it in + // guaranteed that c will receive no more signals.” So we can safely + // close sig here: if there is a send-after-close race, that is a bug in + // Stop and we would like to detect it. + close(sig) + return + + default: + syscall.Kill(syscall.Getpid(), syscall.SIGUSR1) + runtime.Gosched() + } + } + }() + + done := make(chan struct{}) + go func() { + for range sig { + // Receive signals until the sender closes sig. + } + close(done) + }() + + t0 := time.Now() + for t1 := t0; t1.Sub(t0) < dur; t1 = time.Now() { + } // hammering on getting time + + close(stop) + <-done +} + +var ( + checkNotifyContext = flag.Bool("check_notify_ctx", false, "if true, TestNotifyContext will fail if SIGINT is not received.") + ctxNotifyTimes = flag.Int("ctx_notify_times", 1, "number of times a SIGINT signal should be received") +) + +func TestNotifyContextNotifications(t *testing.T) { + if *checkNotifyContext { + ctx, _ := NotifyContext(context.Background(), syscall.SIGINT) + // We want to make sure not to be calling Stop() internally on NotifyContext() when processing a received signal. + // Being able to wait for a number of received system signals allows us to do so. + var wg sync.WaitGroup + n := *ctxNotifyTimes + wg.Add(n) + for i := 0; i < n; i++ { + go func() { + syscall.Kill(syscall.Getpid(), syscall.SIGINT) + wg.Done() + }() + } + wg.Wait() + <-ctx.Done() + fmt.Print("received SIGINT") + // Sleep to give time to simultaneous signals to reach the process. + // These signals must be ignored given stop() is not called on this code. + // We want to guarantee a SIGINT doesn't cause a premature termination of the program. + time.Sleep(settleTime) + return + } + + t.Parallel() + testCases := []struct { + name string + n int // number of times a SIGINT should be notified. + }{ + {"once", 1}, + {"multiple", 10}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var subTimeout time.Duration + if deadline, ok := t.Deadline(); ok { + subTimeout := time.Until(deadline) + subTimeout -= subTimeout / 10 // Leave 10% headroom for cleaning up subprocess. + } + + args := []string{ + "-test.v", + "-test.run=TestNotifyContextNotifications$", + "-check_notify_ctx", + fmt.Sprintf("-ctx_notify_times=%d", tc.n), + } + if subTimeout != 0 { + args = append(args, fmt.Sprintf("-test.timeout=%v", subTimeout)) + } + out, err := exec.Command(os.Args[0], args...).CombinedOutput() + if err != nil { + t.Errorf("ran test with -check_notify_ctx_notification and it failed with %v.\nOutput:\n%s", err, out) + } + if want := []byte("received SIGINT"); !bytes.Contains(out, want) { + t.Errorf("got %q, wanted %q", out, want) + } + }) + } +} + +func TestNotifyContextStop(t *testing.T) { + Ignore(syscall.SIGHUP) + if !Ignored(syscall.SIGHUP) { + t.Errorf("expected SIGHUP to be ignored when explicitly ignoring it.") + } + + parent, cancelParent := context.WithCancel(context.Background()) + defer cancelParent() + c, stop := NotifyContext(parent, syscall.SIGHUP) + defer stop() + + // If we're being notified, then the signal should not be ignored. + if Ignored(syscall.SIGHUP) { + t.Errorf("expected SIGHUP to not be ignored.") + } + + if want, got := "signal.NotifyContext(context.Background.WithCancel, [hangup])", fmt.Sprint(c); want != got { + t.Errorf("c.String() = %q, wanted %q", got, want) + } + + stop() + select { + case <-c.Done(): + if got := c.Err(); got != context.Canceled { + t.Errorf("c.Err() = %q, want %q", got, context.Canceled) + } + case <-time.After(time.Second): + t.Errorf("timed out waiting for context to be done after calling stop") + } +} + +func TestNotifyContextCancelParent(t *testing.T) { + parent, cancelParent := context.WithCancel(context.Background()) + defer cancelParent() + c, stop := NotifyContext(parent, syscall.SIGINT) + defer stop() + + if want, got := "signal.NotifyContext(context.Background.WithCancel, [interrupt])", fmt.Sprint(c); want != got { + t.Errorf("c.String() = %q, want %q", got, want) + } + + cancelParent() + select { + case <-c.Done(): + if got := c.Err(); got != context.Canceled { + t.Errorf("c.Err() = %q, want %q", got, context.Canceled) + } + case <-time.After(time.Second): + t.Errorf("timed out waiting for parent context to be canceled") + } +} + +func TestNotifyContextPrematureCancelParent(t *testing.T) { + parent, cancelParent := context.WithCancel(context.Background()) + defer cancelParent() + + cancelParent() // Prematurely cancel context before calling NotifyContext. + c, stop := NotifyContext(parent, syscall.SIGINT) + defer stop() + + if want, got := "signal.NotifyContext(context.Background.WithCancel, [interrupt])", fmt.Sprint(c); want != got { + t.Errorf("c.String() = %q, want %q", got, want) + } + + select { + case <-c.Done(): + if got := c.Err(); got != context.Canceled { + t.Errorf("c.Err() = %q, want %q", got, context.Canceled) + } + case <-time.After(time.Second): + t.Errorf("timed out waiting for parent context to be canceled") + } +} + +func TestNotifyContextSimultaneousStop(t *testing.T) { + c, stop := NotifyContext(context.Background(), syscall.SIGINT) + defer stop() + + if want, got := "signal.NotifyContext(context.Background, [interrupt])", fmt.Sprint(c); want != got { + t.Errorf("c.String() = %q, want %q", got, want) + } + + var wg sync.WaitGroup + n := 10 + wg.Add(n) + for i := 0; i < n; i++ { + go func() { + stop() + wg.Done() + }() + } + wg.Wait() + select { + case <-c.Done(): + if got := c.Err(); got != context.Canceled { + t.Errorf("c.Err() = %q, want %q", got, context.Canceled) + } + case <-time.After(time.Second): + t.Errorf("expected context to be canceled") + } +} + +func TestNotifyContextStringer(t *testing.T) { + parent, cancelParent := context.WithCancel(context.Background()) + defer cancelParent() + c, stop := NotifyContext(parent, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM) + defer stop() + + want := `signal.NotifyContext(context.Background.WithCancel, [hangup interrupt terminated])` + if got := fmt.Sprint(c); got != want { + t.Errorf("c.String() = %q, want %q", got, want) + } +} + +// #44193 test signal handling while stopping and starting the world. +func TestSignalTrace(t *testing.T) { + done := make(chan struct{}) + quit := make(chan struct{}) + c := make(chan os.Signal, 1) + Notify(c, syscall.SIGHUP) + + // Source and sink for signals busy loop unsynchronized with + // trace starts and stops. We are ultimately validating that + // signals and runtime.(stop|start)TheWorldGC are compatible. + go func() { + defer close(done) + defer Stop(c) + pid := syscall.Getpid() + for { + select { + case <-quit: + return + default: + syscall.Kill(pid, syscall.SIGHUP) + } + waitSig(t, c, syscall.SIGHUP) + } + }() + + for i := 0; i < 100; i++ { + buf := new(bytes.Buffer) + if err := trace.Start(buf); err != nil { + t.Fatalf("[%d] failed to start tracing: %v", i, err) + } + time.After(1 * time.Microsecond) + trace.Stop() + size := buf.Len() + if size == 0 { + t.Fatalf("[%d] trace is empty", i) + } + } + close(quit) + <-done +} diff --git a/src/os/signal/signal_unix.go b/src/os/signal/signal_unix.go new file mode 100644 index 0000000..90a1eca --- /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. + +// +build aix darwin dragonfly freebsd js,wasm linux netbsd openbsd solaris 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..4640428 --- /dev/null +++ b/src/os/signal/signal_windows_test.go @@ -0,0 +1,103 @@ +// 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 ( + "bytes" + "internal/testenv" + "os" + "os/exec" + "path/filepath" + "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, err := os.MkdirTemp("", "TestCtrlBreak") + if err != nil { + t.Fatal("TempDir failed: ", err) + } + defer os.RemoveAll(tmp) + + // 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 := exec.Command(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 := exec.Command(exe) + var b bytes.Buffer + cmd.Stdout = &b + cmd.Stderr = &b + 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, string(b.Bytes())) + } +} |