cmd/dockerd/trap: don't force exit after cleanup
Always calling os.Exit() on clean shutdown may not always be desirable as deferred functions are not run. Let the cleanup callback decide whether or not to call os.Exit() itself. Allow the process to exit the normal way, by returning from func main(). Simplify the trap.Trap implementation. The signal notifications are buffered in a channel so there is little need to spawn a new goroutine for each received signal. With all signals being handled in the same goroutine, there are no longer any concurrency concerns around the interrupt counter. Signed-off-by: Cory Snider <csnider@mirantis.com>
This commit is contained in:
parent
16d5d4b6e1
commit
0f3c5d3893
1 changed files with 24 additions and 27 deletions
|
@ -4,47 +4,44 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const (
|
||||
// Immediately terminate the process when this many SIGINT or SIGTERM
|
||||
// signals are received.
|
||||
forceQuitCount = 3
|
||||
)
|
||||
|
||||
// Trap sets up a simplified signal "trap", appropriate for common
|
||||
// behavior expected from a vanilla unix command-line tool in general
|
||||
// (and the Docker engine in particular).
|
||||
//
|
||||
// - If SIGINT or SIGTERM are received, `cleanup` is called, then the process is terminated.
|
||||
// - If SIGINT or SIGTERM are received 3 times before cleanup is complete, then cleanup is
|
||||
// skipped and the process is terminated immediately (allows force quit of stuck daemon)
|
||||
// The first time a SIGINT or SIGTERM signal is received, `cleanup` is called in
|
||||
// a new goroutine.
|
||||
//
|
||||
// If SIGINT or SIGTERM are received 3 times, the process is terminated
|
||||
// immediately with an exit code of 128 + the signal number.
|
||||
func Trap(cleanup func(), logger interface {
|
||||
Info(args ...interface{})
|
||||
}) {
|
||||
c := make(chan os.Signal, 1)
|
||||
// we will handle INT, TERM here
|
||||
c := make(chan os.Signal, forceQuitCount)
|
||||
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
||||
go func() {
|
||||
interruptCount := uint32(0)
|
||||
var interruptCount int
|
||||
for sig := range c {
|
||||
go func(sig os.Signal) {
|
||||
logger.Info(fmt.Sprintf("Processing signal '%v'", sig))
|
||||
switch sig {
|
||||
case os.Interrupt, syscall.SIGTERM:
|
||||
if atomic.LoadUint32(&interruptCount) < 3 {
|
||||
// Initiate the cleanup only once
|
||||
if atomic.AddUint32(&interruptCount, 1) == 1 {
|
||||
// Call the provided cleanup handler
|
||||
cleanup()
|
||||
os.Exit(0)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// 3 SIGTERM/INT signals received; force exit without cleanup
|
||||
logger.Info("Forcing docker daemon shutdown without cleanup; 3 interrupts received")
|
||||
}
|
||||
logger.Info(fmt.Sprintf("Processing signal '%v'", sig))
|
||||
if interruptCount < forceQuitCount {
|
||||
interruptCount++
|
||||
// Initiate the cleanup only once
|
||||
if interruptCount == 1 {
|
||||
go cleanup()
|
||||
}
|
||||
// for the SIGINT/TERM non-clean shutdown case, exit with 128 + signal #
|
||||
os.Exit(128 + int(sig.(syscall.Signal)))
|
||||
}(sig)
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Info("Forcing docker daemon shutdown without cleanup; 3 interrupts received")
|
||||
os.Exit(128 + int(sig.(syscall.Signal)))
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue