From d5d4ab78195aac74bf1824c0b13cb91d2d7d3f07 Mon Sep 17 00:00:00 2001 From: David du Colombier <0intro@gmail.com> Date: Thu, 22 Jan 2015 23:38:29 +0100 Subject: [PATCH] os/signal, runtime: implement notes on Plan 9 This change is an implementation of the signal runtime and os/signal package on Plan 9. Contrary to Unix, on Plan 9 a signal is called a note and is represented by a string. For this reason, the sigsend and signal_recv functions had to be reimplemented specifically for Plan 9. In order to reuse most of the code and internal interface of the os/signal package, the note strings are mapped to integers. Thanks to Russ Cox for the early review. Change-Id: I95836645efe21942bb1939f43f87fb3c0eaaef1a Reviewed-on: https://go-review.googlesource.com/2164 Reviewed-by: Brad Fitzpatrick Reviewed-by: Rob Pike --- src/os/signal/signal.go | 2 - src/os/signal/signal_plan9.go | 55 +++++++++ src/os/signal/signal_plan9_test.go | 181 +++++++++++++++++++++++++++++ src/os/signal/signal_stub.go | 17 --- src/runtime/os3_plan9.go | 6 +- src/runtime/sigqueue.go | 2 + src/runtime/sigqueue_plan9.go | 115 ++++++++++++++++++ 7 files changed, 356 insertions(+), 22 deletions(-) create mode 100644 src/os/signal/signal_plan9.go create mode 100644 src/os/signal/signal_plan9_test.go delete mode 100644 src/os/signal/signal_stub.go create mode 100644 src/runtime/sigqueue_plan9.go diff --git a/src/os/signal/signal.go b/src/os/signal/signal.go index 3004275495..81906d6f40 100644 --- a/src/os/signal/signal.go +++ b/src/os/signal/signal.go @@ -5,8 +5,6 @@ // Package signal implements access to incoming signals. package signal -// BUG(rsc): This package is not yet implemented on Plan 9. - import ( "os" "sync" diff --git a/src/os/signal/signal_plan9.go b/src/os/signal/signal_plan9.go new file mode 100644 index 0000000000..45355da48a --- /dev/null +++ b/src/os/signal/signal_plan9.go @@ -0,0 +1,55 @@ +// 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) + +// In sig.s; jumps to runtime. +func signal_disable(uint32) +func signal_enable(uint32) +func signal_recv() string + +func init() { + signal_enable(0) // first call - initialize + go 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)) +} diff --git a/src/os/signal/signal_plan9_test.go b/src/os/signal/signal_plan9_test.go new file mode 100644 index 0000000000..10bfdc3ff1 --- /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_stub.go b/src/os/signal/signal_stub.go deleted file mode 100644 index d0a6935ff2..0000000000 --- a/src/os/signal/signal_stub.go +++ /dev/null @@ -1,17 +0,0 @@ -// 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/runtime/os3_plan9.go b/src/runtime/os3_plan9.go index 58ca0e7b2f..facaab2546 100644 --- a/src/runtime/os3_plan9.go +++ b/src/runtime/os3_plan9.go @@ -72,9 +72,9 @@ func sighandler(_ureg *ureg, note *byte, gp *g) int { return _NCONT } if flags&_SigNotify != 0 { - // TODO(ality): See if os/signal wants it. - //if(sigsend(...)) - // return _NCONT; + if sendNote(note) { + return _NCONT + } } if flags&_SigKill != 0 { goto Exit diff --git a/src/runtime/sigqueue.go b/src/runtime/sigqueue.go index fbe3425fa6..5cfc926f67 100644 --- a/src/runtime/sigqueue.go +++ b/src/runtime/sigqueue.go @@ -24,6 +24,8 @@ // unnecessary rechecks of sig.mask, but it cannot lead to missed signals // nor deadlocks. +// +build !plan9 + package runtime import "unsafe" diff --git a/src/runtime/sigqueue_plan9.go b/src/runtime/sigqueue_plan9.go new file mode 100644 index 0000000000..b029a300a8 --- /dev/null +++ b/src/runtime/sigqueue_plan9.go @@ -0,0 +1,115 @@ +// 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. + +// This file implements runtime support for signal handling. + +package runtime + +const qsize = 64 + +var sig struct { + q noteQueue + inuse bool + + lock mutex + note note + sleeping bool +} + +type noteQueue struct { + lock mutex + data [qsize]*byte + ri int + wi int + full bool +} + +func (q *noteQueue) push(item *byte) bool { + lock(&q.lock) + if q.full { + unlock(&q.lock) + return false + } + q.data[q.wi] = item + q.wi++ + if q.wi == qsize { + q.wi = 0 + } + if q.wi == q.ri { + q.full = true + } + unlock(&q.lock) + return true +} + +func (q *noteQueue) pop() *byte { + lock(&q.lock) + q.full = false + if q.ri == q.wi { + unlock(&q.lock) + return nil + } + item := q.data[q.ri] + q.ri++ + if q.ri == qsize { + q.ri = 0 + } + unlock(&q.lock) + return item +} + +// Called from sighandler to send a signal back out of the signal handling thread. +// Reports whether the signal was sent. If not, the caller typically crashes the program. +func sendNote(s *byte) bool { + if !sig.inuse { + return false + } + + // Add signal to outgoing queue. + if !sig.q.push(s) { + return false + } + + lock(&sig.lock) + if sig.sleeping { + sig.sleeping = false + notewakeup(&sig.note) + } + unlock(&sig.lock) + + return true +} + +// Called to receive the next queued signal. +// Must only be called from a single goroutine at a time. +func signal_recv() string { + for { + note := sig.q.pop() + if note != nil { + return gostring(note) + } + + lock(&sig.lock) + sig.sleeping = true + noteclear(&sig.note) + unlock(&sig.lock) + notetsleepg(&sig.note, -1) + } +} + +// Must only be called from a single goroutine at a time. +func signal_enable(s uint32) { + if !sig.inuse { + // The first call to signal_enable is for us + // to use for initialization. It does not pass + // signal information in m. + sig.inuse = true // enable reception of signals; cannot disable + noteclear(&sig.note) + return + } +} + +// Must only be called from a single goroutine at a time. +func signal_disable(s uint32) { +} -- 2.50.0