Skip to content

Commit

Permalink
os/signal: selective signal handling
Browse files Browse the repository at this point in the history
Restore package os/signal, with new API:
Notify replaces Incoming, allowing clients
to ask for certain signals only.  Also, signals
go to everyone who asks, not just one client.

This could plausibly move into package os now
that there are no magic side effects as a result
of the import.

Update runtime for new API: move common Unix
signal handling code into signal_unix.c.
(It's so easy to do this now that we don't have
to edit Makefiles!)

Tested on darwin,linux 386,amd64.

Fixes #1266.

R=r, dsymonds, bradfitz, iant, borman
CC=golang-dev
https://golang.org/cl/3749041
  • Loading branch information
rsc committed Feb 13, 2012
1 parent cdd7e02 commit 35586f7
Show file tree
Hide file tree
Showing 61 changed files with 1,216 additions and 1,266 deletions.
33 changes: 33 additions & 0 deletions doc/go1.html
Expand Up @@ -1363,6 +1363,39 @@ <h3 id="path_filepath">The path/filepath package</h3>
The compiler will catch code using the old interface.
</p>

<h3 id="os/signal">The os/signal package</h3>

<p>
The <code>os/signal</code> package in Go 1 replaces the
<code>Incoming</code> function, which returned a channel
that received all incoming signals,
with the selective <code>Notify</code> function, which asks
for delivery of specific signals on an existing channel.
</p>

<p>
<em>Updating</em>:
Code must be updated by hand.
A literal translation of
</p>
<pre>
c := signal.Incoming()
</pre>
<p>
is
</p>
<pre>
c := make(chan os.Signal)
signal.Notify(c) // ask for all signals
</pre>
<p>
but most code should list the specific signals it wants to handle instead:
</p>
<pre>
c := make(chan os.Signal)
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT)
</pre>

<h3 id="runtime">The runtime package</h3>

<p>
Expand Down
33 changes: 33 additions & 0 deletions doc/go1.tmpl
Expand Up @@ -1266,6 +1266,39 @@ will need to be updated by hand.
The compiler will catch code using the old interface.
</p>

<h3 id="os/signal">The os/signal package</h3>

<p>
The <code>os/signal</code> package in Go 1 replaces the
<code>Incoming</code> function, which returned a channel
that received all incoming signals,
with the selective <code>Notify</code> function, which asks
for delivery of specific signals on an existing channel.
</p>

<p>
<em>Updating</em>:
Code must be updated by hand.
A literal translation of
</p>
<pre>
c := signal.Incoming()
</pre>
<p>
is
</p>
<pre>
c := make(chan os.Signal)
signal.Notify(c) // ask for all signals
</pre>
<p>
but most code should list the specific signals it wants to handle instead:
</p>
<pre>
c := make(chan os.Signal)
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT)
</pre>

<h3 id="runtime">The runtime package</h3>

<p>
Expand Down
37 changes: 0 additions & 37 deletions src/pkg/exp/signal/signal.go

This file was deleted.

24 changes: 0 additions & 24 deletions src/pkg/exp/signal/signal_test.go

This file was deleted.

3 changes: 2 additions & 1 deletion src/pkg/net/http/cgi/host_test.go
Expand Up @@ -19,6 +19,7 @@ import (
"runtime"
"strconv"
"strings"
"syscall"
"testing"
"time"
)
Expand Down Expand Up @@ -355,7 +356,7 @@ func TestCopyError(t *testing.T) {
if err != nil {
return false
}
return p.Signal(os.UnixSignal(0)) == nil
return p.Signal(syscall.Signal(0)) == nil
}

if !childRunning() {
Expand Down
13 changes: 12 additions & 1 deletion src/pkg/os/exec.go
Expand Up @@ -46,11 +46,22 @@ type ProcAttr struct {
Sys *syscall.SysProcAttr
}

// A Signal can represent any operating system signal.
// A Signal represents an operating system signal.
// The usual underlying implementation is operating system-dependent:
// on Unix it is syscall.Signal.
type Signal interface {
String() string
Signal() // to distinguish from other Stringers
}

// The only signal values guaranteed to be present on all systems
// are Interrupt (send the process an interrupt) and
// Kill (force the process to exit).
var (
Interrupt Signal = syscall.SIGINT
Kill Signal = syscall.SIGKILL
)

// Getpid returns the process id of the caller.
func Getpid() int { return syscall.Getpid() }

Expand Down
17 changes: 3 additions & 14 deletions src/pkg/os/exec_posix.go
Expand Up @@ -7,20 +7,9 @@
package os

import (
"runtime"
"syscall"
)

type UnixSignal int32

func (sig UnixSignal) String() string {
s := runtime.Signame(int32(sig))
if len(s) > 0 {
return s
}
return "UnixSignal"
}

// StartProcess starts a new process with the program, arguments and attributes
// specified by name, argv and attr.
//
Expand Down Expand Up @@ -50,7 +39,7 @@ func StartProcess(name string, argv []string, attr *ProcAttr) (p *Process, err e

// Kill causes the Process to exit immediately.
func (p *Process) Kill() error {
return p.Signal(UnixSignal(syscall.SIGKILL))
return p.Signal(Kill)
}

// TODO(rsc): Should os implement its own syscall.WaitStatus
Expand Down Expand Up @@ -118,9 +107,9 @@ func (w *Waitmsg) String() string {
case w.Exited():
res = "exit status " + itod(w.ExitStatus())
case w.Signaled():
res = "signal " + itod(w.Signal())
res = "signal " + itod(int(w.Signal()))
case w.Stopped():
res = "stop signal " + itod(w.StopSignal())
res = "stop signal " + itod(int(w.StopSignal()))
if w.StopSignal() == syscall.SIGTRAP && w.TrapCause() != 0 {
res += " (trap " + itod(w.TrapCause()) + ")"
}
Expand Down
6 changes: 5 additions & 1 deletion src/pkg/os/exec_unix.go
Expand Up @@ -57,7 +57,11 @@ func (p *Process) Signal(sig Signal) error {
if p.done {
return errors.New("os: process already finished")
}
if e := syscall.Kill(p.Pid, int(sig.(UnixSignal))); e != nil {
s, ok := sig.(syscall.Signal)
if !ok {
return errors.New("os: unsupported signal type")
}
if e := syscall.Kill(p.Pid, s); e != nil {
return e
}
return nil
Expand Down
3 changes: 2 additions & 1 deletion src/pkg/os/exec_windows.go
Expand Up @@ -37,10 +37,11 @@ func (p *Process) Signal(sig Signal) error {
if p.done {
return errors.New("os: process already finished")
}
if us, ok := sig.(UnixSignal); ok && us == syscall.SIGKILL {
if sig == Kill {
e := syscall.TerminateProcess(syscall.Handle(p.handle), 1)
return NewSyscallError("TerminateProcess", e)
}
// TODO(rsc): Handle Interrupt too?
return syscall.Errno(syscall.EWINDOWS)
}

Expand Down
16 changes: 16 additions & 0 deletions src/pkg/os/signal/sig.s
@@ -0,0 +1,16 @@
// 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.

#ifdef GOARCH_arm
#define JMP B
#endif

TEXT ·signal_enable(SB),7,$0
JMP runtime·signal_enable(SB)

TEXT ·signal_recv(SB),7,$0
JMP runtime·signal_recv(SB)

72 changes: 72 additions & 0 deletions src/pkg/os/signal/signal.go
@@ -0,0 +1,72 @@
// 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 and Windows.

import (
"os"
"sync"
)

var handlers struct {
sync.Mutex
list []handler
}

type handler struct {
c chan<- os.Signal
sig os.Signal
all bool
}

// 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.
//
func Notify(c chan<- os.Signal, sig ...os.Signal) {
if c == nil {
panic("os/signal: Notify using nil channel")
}

handlers.Lock()
defer handlers.Unlock()
if len(sig) == 0 {
enableSignal(nil)
handlers.list = append(handlers.list, handler{c: c, all: true})
} else {
for _, s := range sig {
// We use nil as a special wildcard value for enableSignal,
// so filter it out of the list of arguments. This is safe because
// we will never get an incoming nil signal, so discarding the
// registration cannot affect the observed behavior.
if s != nil {
enableSignal(s)
handlers.list = append(handlers.list, handler{c: c, sig: s})
}
}
}
}

func process(sig os.Signal) {
handlers.Lock()
defer handlers.Unlock()

for _, h := range handlers.list {
if h.all || h.sig == sig {
// send but do not block for it
select {
case h.c <- sig:
default:
}
}
}
}

0 comments on commit 35586f7

Please sign in to comment.