blob: 61600cbc84d04f41424bc859706315213fd2fb29 [file] [log] [blame]
Jiri Simsad7616c92015-03-24 23:44:30 -07001// Copyright 2015 The Vanadium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
Todd Wang8c4e5cc2015-04-09 11:30:52 -07005// Package signals implements utilities for managing process shutdown with
6// support for signal-handling.
Jiri Simsa5293dcb2014-05-10 09:56:38 -07007package signals
8
Todd Wang8c4e5cc2015-04-09 11:30:52 -07009// TODO(caprita): Rename the function to Shutdown() and the package to shutdown
10// since it's not just signals anymore.
11
Jiri Simsa5293dcb2014-05-10 09:56:38 -070012import (
13 "os"
14 "os/signal"
15 "syscall"
Bogdan Caprita29a3b352015-01-16 16:28:49 -080016 "time"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070017
Jiri Simsa6ac95222015-02-23 16:11:49 -080018 "v.io/v23"
19 "v.io/v23/context"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070020)
21
22type stopSignal string
23
24func (stopSignal) Signal() {}
25func (s stopSignal) String() string { return string(s) }
26
27const (
28 STOP = stopSignal("")
29 DoubleStopExitCode = 1
30)
31
Bogdan Caprita29a3b352015-01-16 16:28:49 -080032// TODO(caprita): Needless to say, this is a hack. The motivator was getting
33// the device manager (run by the security agent) to shut down cleanly when the
34// process group containing both the agent and device manager receives a signal
35// (and the agent propagates that signal to the child). We should be able to
36// finesse this by demonizing the device manager and/or being smarter about how
37// and when the agent sends the signal to the child.
38
39// SameSignalTimeWindow specifies the time window during which multiple
40// deliveries of the same signal are counted as one signal. If set to zero, no
41// such de-duping occurs. This is useful in situations where a process receives
42// a signal explicitly sent by its parent when the parent receives the signal,
43// but also receives it independently by virtue of being part of the same
44// process group.
45//
46// This is a variable, so that I can be set appropriately. Note, there is no
47// locking around it, the assumption being that it's set during initialization
48// and never reset afterwards.
49var SameSignalTimeWindow time.Duration
50
Jiri Simsa5293dcb2014-05-10 09:56:38 -070051// defaultSignals returns a set of platform-specific signals that an application
52// is encouraged to listen on.
53func defaultSignals() []os.Signal {
54 return []os.Signal{syscall.SIGTERM, syscall.SIGINT, STOP}
55}
56
Jiri Simsa5293dcb2014-05-10 09:56:38 -070057// ShutdownOnSignals registers signal handlers for the specified signals, or, if
58// none are specified, the default signals. The first signal received will be
59// made available on the returned channel; upon receiving a second signal, the
60// process will exit.
Suharsh Sivakumar946f64d2015-01-08 10:48:13 -080061func ShutdownOnSignals(ctx *context.T, signals ...os.Signal) <-chan os.Signal {
Jiri Simsa5293dcb2014-05-10 09:56:38 -070062 if len(signals) == 0 {
63 signals = defaultSignals()
64 }
65 // At least a buffer of length two so that we don't drop the first two
66 // signals we get on account of the channel being full.
67 ch := make(chan os.Signal, 2)
68 sawStop := false
Bogdan Caprita1002ba42014-06-06 19:24:40 -070069 var signalsNoStop []os.Signal
70 for _, s := range signals {
71 switch s {
72 case STOP:
73 if !sawStop {
74 sawStop = true
Suharsh Sivakumar946f64d2015-01-08 10:48:13 -080075 if ctx != nil {
Bogdan Caprita1002ba42014-06-06 19:24:40 -070076 stopWaiter := make(chan string, 1)
Cosmos Nicolaoue9c622d2015-07-10 11:09:42 -070077 v23.GetAppCycle(ctx).WaitForStop(ctx, stopWaiter)
Bogdan Caprita1002ba42014-06-06 19:24:40 -070078 go func() {
79 for {
80 ch <- stopSignal(<-stopWaiter)
81 }
82 }()
83 }
Jiri Simsa5293dcb2014-05-10 09:56:38 -070084 }
Bogdan Caprita1002ba42014-06-06 19:24:40 -070085 default:
86 signalsNoStop = append(signalsNoStop, s)
Jiri Simsa5293dcb2014-05-10 09:56:38 -070087 }
88 }
Bogdan Caprita1002ba42014-06-06 19:24:40 -070089 if len(signalsNoStop) > 0 {
90 signal.Notify(ch, signalsNoStop...)
Jiri Simsa5293dcb2014-05-10 09:56:38 -070091 }
92 // At least a buffer of length one so that we don't block on ret <- sig.
93 ret := make(chan os.Signal, 1)
94 go func() {
95 // First signal received.
96 sig := <-ch
Bogdan Caprita29a3b352015-01-16 16:28:49 -080097 sigTime := time.Now()
Jiri Simsa5293dcb2014-05-10 09:56:38 -070098 ret <- sig
99 // Wait for a second signal, and force an exit if the process is
100 // still executing cleanup code.
Bogdan Caprita29a3b352015-01-16 16:28:49 -0800101 for {
102 secondSig := <-ch
103 // If signal de-duping is enabled, ignore the signal if
104 // it's the same signal and has occured within the
105 // specified time window.
106 if SameSignalTimeWindow <= 0 || secondSig.String() != sig.String() || sigTime.Add(SameSignalTimeWindow).Before(time.Now()) {
107 os.Exit(DoubleStopExitCode)
108 }
109 }
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700110 }()
111 return ret
112}