summaryrefslogtreecommitdiff
path: root/src/os/signal
diff options
context:
space:
mode:
Diffstat (limited to 'src/os/signal')
-rw-r--r--src/os/signal/example_test.go23
-rw-r--r--src/os/signal/sig.s23
-rw-r--r--src/os/signal/signal.go131
-rw-r--r--src/os/signal/signal_stub.go17
-rw-r--r--src/os/signal/signal_test.go208
-rw-r--r--src/os/signal/signal_unix.go53
-rw-r--r--src/os/signal/signal_windows_test.go103
7 files changed, 558 insertions, 0 deletions
diff --git a/src/os/signal/example_test.go b/src/os/signal/example_test.go
new file mode 100644
index 000000000..079ee5070
--- /dev/null
+++ b/src/os/signal/example_test.go
@@ -0,0 +1,23 @@
+// 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, os.Kill)
+
+ // Block until a signal is received.
+ s := <-c
+ fmt.Println("Got signal:", s)
+}
diff --git a/src/os/signal/sig.s b/src/os/signal/sig.s
new file mode 100644
index 000000000..d54c284b5
--- /dev/null
+++ b/src/os/signal/sig.s
@@ -0,0 +1,23 @@
+// 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.
+
+// Assembly to get into package runtime without using exported symbols.
+
+// +build amd64 amd64p32 arm 386
+
+#include "textflag.h"
+
+#ifdef GOARCH_arm
+#define JMP B
+#endif
+
+TEXT ·signal_disable(SB),NOSPLIT,$0
+ JMP runtime·signal_disable(SB)
+
+TEXT ·signal_enable(SB),NOSPLIT,$0
+ JMP runtime·signal_enable(SB)
+
+TEXT ·signal_recv(SB),NOSPLIT,$0
+ JMP runtime·signal_recv(SB)
+
diff --git a/src/os/signal/signal.go b/src/os/signal/signal.go
new file mode 100644
index 000000000..300427549
--- /dev/null
+++ b/src/os/signal/signal.go
@@ -0,0 +1,131 @@
+// 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 implements access to incoming signals.
+package signal
+
+// BUG(rsc): This package is not yet implemented on Plan 9.
+
+import (
+ "os"
+ "sync"
+)
+
+var handlers struct {
+ sync.Mutex
+ m map[chan<- os.Signal]*handler
+ ref [numSig]int64
+}
+
+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)
+}
+
+// Notify causes package signal to relay incoming signals to c.
+// If no signals are listed, all incoming signals will be relayed to c.
+// Otherwise, just the listed 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)
+ }
+ handlers.ref[n]++
+ }
+ }
+
+ if len(sig) == 0 {
+ for n := 0; n < numSig; n++ {
+ add(n)
+ }
+ } else {
+ for _, s := range sig {
+ add(signum(s))
+ }
+ }
+}
+
+// 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()
+ defer handlers.Unlock()
+
+ h := handlers.m[c]
+ if h == nil {
+ 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)
+ }
+ }
+ }
+}
+
+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:
+ }
+ }
+ }
+}
diff --git a/src/os/signal/signal_stub.go b/src/os/signal/signal_stub.go
new file mode 100644
index 000000000..d0a6935ff
--- /dev/null
+++ b/src/os/signal/signal_stub.go
@@ -0,0 +1,17 @@
+// 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 plan9
+
+package signal
+
+import "os"
+
+const numSig = 0
+
+func signum(sig os.Signal) int { return -1 }
+
+func disableSignal(int) {}
+
+func enableSignal(int) {}
diff --git a/src/os/signal/signal_test.go b/src/os/signal/signal_test.go
new file mode 100644
index 000000000..22337a72d
--- /dev/null
+++ b/src/os/signal/signal_test.go
@@ -0,0 +1,208 @@
+// 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 darwin dragonfly freebsd linux netbsd openbsd solaris
+
+package signal
+
+import (
+ "flag"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "runtime"
+ "strconv"
+ "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 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.
+ c1 := make(chan os.Signal, 1)
+ Notify(c1)
+
+ // Send this process a SIGWINCH
+ t.Logf("sigwinch...")
+ syscall.Kill(syscall.Getpid(), syscall.SIGWINCH)
+ waitSig(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)
+ waitSig(t, c1, syscall.SIGHUP)
+ t.Logf("sighup...")
+ syscall.Kill(syscall.Getpid(), syscall.SIGHUP)
+ waitSig(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))
+ done := make(chan bool)
+ finished := make(chan bool)
+ go func() {
+ sig := make(chan os.Signal, 1)
+ Notify(sig, syscall.SIGUSR1)
+ defer Stop(sig)
+ Loop:
+ for {
+ select {
+ case <-sig:
+ case <-done:
+ break Loop
+ }
+ }
+ finished <- true
+ }()
+ go func() {
+ Loop:
+ for {
+ select {
+ case <-done:
+ break Loop
+ default:
+ syscall.Kill(syscall.Getpid(), syscall.SIGUSR1)
+ runtime.Gosched()
+ }
+ }
+ finished <- true
+ }()
+ time.Sleep(dur)
+ close(done)
+ <-finished
+ <-finished
+ // When run with 'go test -cpu=1,2,4' SIGUSR1 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)
+}
+
+var sendUncaughtSighup = flag.Int("send_uncaught_sighup", 0, "send uncaught SIGHUP during TestStop")
+
+// Test that Stop cancels the channel's registrations.
+func TestStop(t *testing.T) {
+ sigs := []syscall.Signal{
+ syscall.SIGWINCH,
+ syscall.SIGHUP,
+ }
+
+ for _, sig := range sigs {
+ // Send the signal.
+ // If it's SIGWINCH, we should not see it.
+ // If it's SIGHUP, maybe we'll die. Let the flag tell us what to do.
+ if sig != syscall.SIGHUP || *sendUncaughtSighup == 1 {
+ syscall.Kill(syscall.Getpid(), sig)
+ }
+ time.Sleep(100 * time.Millisecond)
+
+ // Ask for signal
+ c := make(chan os.Signal, 1)
+ Notify(c, sig)
+ defer Stop(c)
+
+ // Send this process that signal
+ syscall.Kill(syscall.Getpid(), sig)
+ waitSig(t, c, 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 SIGWINCH, we should not see it.
+ // If it's SIGHUP, maybe we'll die. Let the flag tell us what to do.
+ if sig != syscall.SIGHUP || *sendUncaughtSighup == 2 {
+ syscall.Kill(syscall.Getpid(), sig)
+ }
+
+ select {
+ case s := <-c:
+ t.Fatalf("unexpected signal %v", s)
+ case <-time.After(100 * time.Millisecond):
+ // nothing to read - good
+ }
+ }
+}
+
+// Test that when run under nohup, an uncaught SIGHUP does not kill the program,
+// but a
+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.
+
+ for i := 1; i <= 2; i++ {
+ out, err := exec.Command(os.Args[0], "-test.run=TestStop", "-send_uncaught_sighup="+strconv.Itoa(i)).CombinedOutput()
+ if err == nil {
+ t.Fatalf("ran test with -send_uncaught_sighup=%d and it succeeded: expected failure.\nOutput:\n%s", i, 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")
+ }
+
+ for i := 1; i <= 2; i++ {
+ os.Remove("nohup.out")
+ out, err := exec.Command("/usr/bin/nohup", os.Args[0], "-test.run=TestStop", "-send_uncaught_sighup="+strconv.Itoa(i)).CombinedOutput()
+
+ data, _ := ioutil.ReadFile("nohup.out")
+ os.Remove("nohup.out")
+ if err != nil {
+ t.Fatalf("ran test with -send_uncaught_sighup=%d under nohup and it failed: expected success.\nError: %v\nOutput:\n%s%s", i, err, out, data)
+ }
+ }
+}
diff --git a/src/os/signal/signal_unix.go b/src/os/signal/signal_unix.go
new file mode 100644
index 000000000..94b8ab3dd
--- /dev/null
+++ b/src/os/signal/signal_unix.go
@@ -0,0 +1,53 @@
+// 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 darwin dragonfly freebsd linux nacl netbsd openbsd solaris windows
+
+package signal
+
+import (
+ "os"
+ "syscall"
+)
+
+// In assembly.
+func signal_disable(uint32)
+func signal_enable(uint32)
+func signal_recv() uint32
+
+func loop() {
+ for {
+ process(syscall.Signal(signal_recv()))
+ }
+}
+
+func init() {
+ signal_enable(0) // first call - initialize
+ go 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))
+}
diff --git a/src/os/signal/signal_windows_test.go b/src/os/signal/signal_windows_test.go
new file mode 100644
index 000000000..f3e6706b7
--- /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"
+ "io/ioutil"
+ "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 := ioutil.TempDir("", "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("go", "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()))
+ }
+}