package graceful
|
|
|
|
import (
|
|
"os"
|
|
"os/signal"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/zenazn/goji/graceful/listener"
|
|
)
|
|
|
|
var mu sync.Mutex // protects everything that follows
|
|
var listeners = make([]*listener.T, 0)
|
|
var prehooks = make([]func(), 0)
|
|
var posthooks = make([]func(), 0)
|
|
var closing int32
|
|
var doubleKick, timeout time.Duration
|
|
|
|
var wait = make(chan struct{})
|
|
var stdSignals = []os.Signal{os.Interrupt}
|
|
var sigchan = make(chan os.Signal, 1)
|
|
|
|
// HandleSignals installs signal handlers for a set of standard signals. By
|
|
// default, this set only includes keyboard interrupts, however when the package
|
|
// detects that it is running under Einhorn, a SIGUSR2 handler is installed as
|
|
// well.
|
|
func HandleSignals() {
|
|
AddSignal(stdSignals...)
|
|
}
|
|
|
|
// AddSignal adds the given signal to the set of signals that trigger a graceful
|
|
// shutdown.
|
|
func AddSignal(sig ...os.Signal) {
|
|
signal.Notify(sigchan, sig...)
|
|
}
|
|
|
|
// ResetSignals resets the list of signals that trigger a graceful shutdown.
|
|
func ResetSignals() {
|
|
signal.Stop(sigchan)
|
|
}
|
|
|
|
// 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()) {
|
|
mu.Lock()
|
|
defer mu.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 Hijacked 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()) {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
posthooks = append(posthooks, f)
|
|
}
|
|
|
|
// Shutdown manually triggers a shutdown from your application. Like Wait,
|
|
// blocks until all connections have gracefully shut down.
|
|
func Shutdown() {
|
|
shutdown(false)
|
|
}
|
|
|
|
// ShutdownNow triggers an immediate shutdown from your application. All
|
|
// connections (not just those that are idle) are immediately closed, even if
|
|
// they are in the middle of serving a request.
|
|
func ShutdownNow() {
|
|
shutdown(true)
|
|
}
|
|
|
|
// DoubleKickWindow sets the length of the window during which two back-to-back
|
|
// signals are treated as an especially urgent or forceful request to exit
|
|
// (i.e., ShutdownNow instead of Shutdown). Signals delivered more than this
|
|
// duration apart are treated as separate requests to exit gracefully as usual.
|
|
//
|
|
// Setting DoubleKickWindow to 0 disables the feature.
|
|
func DoubleKickWindow(d time.Duration) {
|
|
if d < 0 {
|
|
return
|
|
}
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
doubleKick = d
|
|
}
|
|
|
|
// Timeout sets the maximum amount of time package graceful will wait for
|
|
// connections to gracefully shut down after receiving a signal. After this
|
|
// timeout, connections will be forcefully shut down (similar to calling
|
|
// ShutdownNow).
|
|
//
|
|
// Setting Timeout to 0 disables the feature.
|
|
func Timeout(d time.Duration) {
|
|
if d < 0 {
|
|
return
|
|
}
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
timeout = d
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
func init() {
|
|
go sigLoop()
|
|
}
|
|
func sigLoop() {
|
|
var last time.Time
|
|
for {
|
|
<-sigchan
|
|
now := time.Now()
|
|
mu.Lock()
|
|
force := doubleKick != 0 && now.Sub(last) < doubleKick
|
|
if t := timeout; t != 0 && !force {
|
|
go func() {
|
|
time.Sleep(t)
|
|
shutdown(true)
|
|
}()
|
|
}
|
|
mu.Unlock()
|
|
go shutdown(force)
|
|
last = now
|
|
}
|
|
}
|
|
|
|
var preOnce, closeOnce, forceOnce, postOnce, notifyOnce sync.Once
|
|
|
|
func shutdown(force bool) {
|
|
preOnce.Do(func() {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
for _, f := range prehooks {
|
|
f()
|
|
}
|
|
})
|
|
|
|
if force {
|
|
forceOnce.Do(func() {
|
|
closeListeners(force)
|
|
})
|
|
} else {
|
|
closeOnce.Do(func() {
|
|
closeListeners(force)
|
|
})
|
|
}
|
|
|
|
postOnce.Do(func() {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
for _, f := range posthooks {
|
|
f()
|
|
}
|
|
})
|
|
|
|
notifyOnce.Do(func() {
|
|
close(wait)
|
|
})
|
|
}
|
|
|
|
func closeListeners(force bool) {
|
|
atomic.StoreInt32(&closing, 1)
|
|
|
|
var wg sync.WaitGroup
|
|
defer wg.Wait()
|
|
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
wg.Add(len(listeners))
|
|
|
|
for _, l := range listeners {
|
|
go func(l *listener.T) {
|
|
defer wg.Done()
|
|
l.Close()
|
|
if force {
|
|
l.DrainAll()
|
|
} else {
|
|
l.Drain()
|
|
}
|
|
}(l)
|
|
}
|
|
}
|