|
|
package graceful
|
|
|
|
|
|
import (
|
|
|
"log"
|
|
|
"os"
|
|
|
"os/signal"
|
|
|
"sync"
|
|
|
)
|
|
|
|
|
|
// This is the channel that the connections select on. When it is closed, the
|
|
|
// connections should gracefully exit.
|
|
|
var kill = make(chan struct{})
|
|
|
|
|
|
// This is the channel that the Wait() function selects on. It should only be
|
|
|
// closed once all the posthooks have been called.
|
|
|
var wait = make(chan struct{})
|
|
|
|
|
|
// This is the WaitGroup that indicates when all the connections have gracefully
|
|
|
// shut down.
|
|
|
var wg sync.WaitGroup
|
|
|
|
|
|
// This lock protects the list of pre- and post- hooks below.
|
|
|
var hookLock sync.Mutex
|
|
|
var prehooks = make([]func(), 0)
|
|
|
var posthooks = make([]func(), 0)
|
|
|
|
|
|
var sigchan = make(chan os.Signal, 1)
|
|
|
|
|
|
func init() {
|
|
|
AddSignal(os.Interrupt)
|
|
|
go waitForSignal()
|
|
|
}
|
|
|
|
|
|
// AddSignal adds the given signal to the set of signals that trigger a graceful
|
|
|
// shutdown. Note that for convenience the default interrupt (SIGINT) handler is
|
|
|
// installed at package load time, and unless you call ResetSignals() will be
|
|
|
// listened for in addition to any signals you provide by calling this function.
|
|
|
func AddSignal(sig ...os.Signal) {
|
|
|
signal.Notify(sigchan, sig...)
|
|
|
}
|
|
|
|
|
|
// ResetSignals resets the list of signals that trigger a graceful shutdown.
|
|
|
// Useful if, for instance, you don't want to use the default interrupt (SIGINT)
|
|
|
// handler. Since we necessarily install the SIGINT handler before you have a
|
|
|
// chance to call ResetSignals(), there will be a brief window during which the
|
|
|
// set of signals this package listens for will not be as you intend. Therefore,
|
|
|
// if you intend on using this function, we encourage you to call it as soon as
|
|
|
// possible.
|
|
|
func ResetSignals() {
|
|
|
signal.Stop(sigchan)
|
|
|
}
|
|
|
|
|
|
type userShutdown struct{}
|
|
|
|
|
|
func (u userShutdown) String() string {
|
|
|
return "application initiated shutdown"
|
|
|
}
|
|
|
func (u userShutdown) Signal() {}
|
|
|
|
|
|
// Shutdown manually trigger a shutdown from your application. Like Wait(),
|
|
|
// blocks until all connections have gracefully shut down.
|
|
|
func Shutdown() {
|
|
|
sigchan <- userShutdown{}
|
|
|
<-wait
|
|
|
}
|
|
|
|
|
|
// PreHook registers a function to be called before any of this package's normal
|
|
|
// shutdown actions. All listeners will be called in the order they were added,
|
|
|
// from a single goroutine.
|
|
|
func PreHook(f func()) {
|
|
|
hookLock.Lock()
|
|
|
defer hookLock.Unlock()
|
|
|
|
|
|
prehooks = append(prehooks, f)
|
|
|
}
|
|
|
|
|
|
// PostHook registers a function to be called after all of this package's normal
|
|
|
// shutdown actions. All listeners will be called in the order they were added,
|
|
|
// from a single goroutine, and are guaranteed to be called after all listening
|
|
|
// connections have been closed, but before Wait() returns.
|
|
|
//
|
|
|
// If you've Hijack()ed any connections that must be gracefully shut down in
|
|
|
// some other way (since this library disowns all hijacked connections), it's
|
|
|
// reasonable to use a PostHook() to signal and wait for them.
|
|
|
func PostHook(f func()) {
|
|
|
hookLock.Lock()
|
|
|
defer hookLock.Unlock()
|
|
|
|
|
|
posthooks = append(posthooks, f)
|
|
|
}
|
|
|
|
|
|
func waitForSignal() {
|
|
|
sig := <-sigchan
|
|
|
log.Printf("Received %v, gracefully shutting down!", sig)
|
|
|
|
|
|
hookLock.Lock()
|
|
|
defer hookLock.Unlock()
|
|
|
|
|
|
for _, f := range prehooks {
|
|
|
f()
|
|
|
}
|
|
|
|
|
|
close(kill)
|
|
|
wg.Wait()
|
|
|
|
|
|
for _, f := range posthooks {
|
|
|
f()
|
|
|
}
|
|
|
|
|
|
close(wait)
|
|
|
}
|
|
|
|
|
|
// Wait for all connections to gracefully shut down. This is commonly called at
|
|
|
// the bottom of the main() function to prevent the program from exiting
|
|
|
// prematurely.
|
|
|
func Wait() {
|
|
|
<-wait
|
|
|
}
|