blob: e24e1c3548eaf60355f1adb6fadec130db56b342 [file] [log] [blame]
package signals
import (
"os"
"os/signal"
"syscall"
"veyron.io/veyron/veyron2/rt"
)
type stopSignal string
func (stopSignal) Signal() {}
func (s stopSignal) String() string { return string(s) }
const (
STOP = stopSignal("")
DoubleStopExitCode = 1
)
// defaultSignals returns a set of platform-specific signals that an application
// is encouraged to listen on.
func defaultSignals() []os.Signal {
return []os.Signal{syscall.SIGTERM, syscall.SIGINT, STOP}
}
// TODO(caprita): Rename this to Shutdown() and the package to shutdown since
// it's not just signals anymore.
// ShutdownOnSignals registers signal handlers for the specified signals, or, if
// none are specified, the default signals. The first signal received will be
// made available on the returned channel; upon receiving a second signal, the
// process will exit.
func ShutdownOnSignals(signals ...os.Signal) <-chan os.Signal {
if len(signals) == 0 {
signals = defaultSignals()
}
// At least a buffer of length two so that we don't drop the first two
// signals we get on account of the channel being full.
ch := make(chan os.Signal, 2)
sawStop := false
var signalsNoStop []os.Signal
for _, s := range signals {
switch s {
case STOP:
if !sawStop {
sawStop = true
if r := rt.R(); r != nil {
stopWaiter := make(chan string, 1)
r.AppCycle().WaitForStop(stopWaiter)
go func() {
for {
ch <- stopSignal(<-stopWaiter)
}
}()
}
}
default:
signalsNoStop = append(signalsNoStop, s)
}
}
if len(signalsNoStop) > 0 {
signal.Notify(ch, signalsNoStop...)
}
// At least a buffer of length one so that we don't block on ret <- sig.
ret := make(chan os.Signal, 1)
go func() {
// First signal received.
sig := <-ch
ret <- sig
// Wait for a second signal, and force an exit if the process is
// still executing cleanup code.
<-ch
os.Exit(DoubleStopExitCode)
}()
return ret
}