You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
149 lines
4.0 KiB
149 lines
4.0 KiB
// Copyright (c) 2013-2017 The btcsuite developers |
|
// Copyright (c) 2015-2016 The Decred developers |
|
// Heavily inspired by https://github.com/btcsuite/btcd/blob/master/signal.go |
|
// Copyright (C) 2015-2017 The Lightning Network Developers |
|
|
|
package signal |
|
|
|
import ( |
|
"errors" |
|
"os" |
|
"os/signal" |
|
"sync/atomic" |
|
"syscall" |
|
) |
|
|
|
var ( |
|
// started indicates whether we have started our main interrupt handler. |
|
// This field should be used atomically. |
|
started int32 |
|
) |
|
|
|
// Interceptor contains channels and methods regarding application shutdown |
|
// and interrupt signals |
|
type Interceptor struct { |
|
// interruptChannel is used to receive SIGINT (Ctrl+C) signals. |
|
interruptChannel chan os.Signal |
|
|
|
// shutdownChannel is closed once the main interrupt handler exits. |
|
shutdownChannel chan struct{} |
|
|
|
// shutdownRequestChannel is used to request the daemon to shutdown |
|
// gracefully, similar to when receiving SIGINT. |
|
shutdownRequestChannel chan struct{} |
|
|
|
// quit is closed when instructing the main interrupt handler to exit. |
|
quit chan struct{} |
|
} |
|
|
|
// Intercept starts the interception of interrupt signals and returns an `Interceptor` instance. |
|
// Note that any previous active interceptor must be stopped before a new one can be created |
|
func Intercept() (Interceptor, error) { |
|
if !atomic.CompareAndSwapInt32(&started, 0, 1) { |
|
return Interceptor{}, errors.New("intercept already started") |
|
} |
|
|
|
channels := Interceptor{ |
|
interruptChannel: make(chan os.Signal, 1), |
|
shutdownChannel: make(chan struct{}), |
|
shutdownRequestChannel: make(chan struct{}), |
|
quit: make(chan struct{}), |
|
} |
|
|
|
signalsToCatch := []os.Signal{ |
|
os.Interrupt, |
|
os.Kill, |
|
syscall.SIGTERM, |
|
syscall.SIGQUIT, |
|
} |
|
signal.Notify(channels.interruptChannel, signalsToCatch...) |
|
go channels.mainInterruptHandler() |
|
|
|
return channels, nil |
|
} |
|
|
|
// mainInterruptHandler listens for SIGINT (Ctrl+C) signals on the |
|
// interruptChannel and shutdown requests on the shutdownRequestChannel, and |
|
// invokes the registered interruptCallbacks accordingly. It also listens for |
|
// callback registration. |
|
// It must be run as a goroutine. |
|
func (c *Interceptor) mainInterruptHandler() { |
|
defer atomic.StoreInt32(&started, 0) |
|
// isShutdown is a flag which is used to indicate whether or not |
|
// the shutdown signal has already been received and hence any future |
|
// attempts to add a new interrupt handler should invoke them |
|
// immediately. |
|
var isShutdown bool |
|
|
|
// shutdown invokes the registered interrupt handlers, then signals the |
|
// shutdownChannel. |
|
shutdown := func() { |
|
// Ignore more than one shutdown signal. |
|
if isShutdown { |
|
log.Infof("Already shutting down...") |
|
return |
|
} |
|
isShutdown = true |
|
log.Infof("Shutting down...") |
|
|
|
// Signal the main interrupt handler to exit, and stop accept |
|
// post-facto requests. |
|
close(c.quit) |
|
} |
|
|
|
for { |
|
select { |
|
case signal := <-c.interruptChannel: |
|
log.Infof("Received %v", signal) |
|
shutdown() |
|
|
|
case <-c.shutdownRequestChannel: |
|
log.Infof("Received shutdown request.") |
|
shutdown() |
|
|
|
case <-c.quit: |
|
log.Infof("Gracefully shutting down.") |
|
close(c.shutdownChannel) |
|
signal.Stop(c.interruptChannel) |
|
return |
|
} |
|
} |
|
} |
|
|
|
// Listening returns true if the main interrupt handler has been started, and |
|
// has not been killed. |
|
func (c *Interceptor) Listening() bool { |
|
// If our started field is not set, we are not yet listening for |
|
// interrupts. |
|
if atomic.LoadInt32(&started) != 1 { |
|
return false |
|
} |
|
|
|
// If we have started our main goroutine, we check whether we have |
|
// stopped it yet. |
|
return c.Alive() |
|
} |
|
|
|
// Alive returns true if the main interrupt handler has not been killed. |
|
func (c *Interceptor) Alive() bool { |
|
select { |
|
case <-c.quit: |
|
return false |
|
default: |
|
return true |
|
} |
|
} |
|
|
|
// RequestShutdown initiates a graceful shutdown from the application. |
|
func (c *Interceptor) RequestShutdown() { |
|
select { |
|
case c.shutdownRequestChannel <- struct{}{}: |
|
case <-c.quit: |
|
} |
|
} |
|
|
|
// ShutdownChannel returns the channel that will be closed once the main |
|
// interrupt handler has exited. |
|
func (c *Interceptor) ShutdownChannel() <-chan struct{} { |
|
return c.shutdownChannel |
|
}
|
|
|