github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/pkg/signal/trap.go (about) 1 package signal // import "github.com/demonoid81/moby/pkg/signal" 2 3 import ( 4 "fmt" 5 "os" 6 gosignal "os/signal" 7 "path/filepath" 8 "runtime" 9 "strings" 10 "sync/atomic" 11 "syscall" 12 "time" 13 14 "github.com/pkg/errors" 15 ) 16 17 // Trap sets up a simplified signal "trap", appropriate for common 18 // behavior expected from a vanilla unix command-line tool in general 19 // (and the Docker engine in particular). 20 // 21 // * If SIGINT or SIGTERM are received, `cleanup` is called, then the process is terminated. 22 // * If SIGINT or SIGTERM are received 3 times before cleanup is complete, then cleanup is 23 // skipped and the process is terminated immediately (allows force quit of stuck daemon) 24 // * A SIGQUIT always causes an exit without cleanup, with a goroutine dump preceding exit. 25 // * Ignore SIGPIPE events. These are generated by systemd when journald is restarted while 26 // the docker daemon is not restarted and also running under systemd. 27 // Fixes https://github.com/demonoid81/moby/issues/19728 28 // 29 func Trap(cleanup func(), logger interface { 30 Info(args ...interface{}) 31 }) { 32 c := make(chan os.Signal, 1) 33 // we will handle INT, TERM, QUIT, SIGPIPE here 34 signals := []os.Signal{os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGPIPE} 35 gosignal.Notify(c, signals...) 36 go func() { 37 interruptCount := uint32(0) 38 for sig := range c { 39 if sig == syscall.SIGPIPE { 40 continue 41 } 42 43 go func(sig os.Signal) { 44 logger.Info(fmt.Sprintf("Processing signal '%v'", sig)) 45 switch sig { 46 case os.Interrupt, syscall.SIGTERM: 47 if atomic.LoadUint32(&interruptCount) < 3 { 48 // Initiate the cleanup only once 49 if atomic.AddUint32(&interruptCount, 1) == 1 { 50 // Call the provided cleanup handler 51 cleanup() 52 os.Exit(0) 53 } else { 54 return 55 } 56 } else { 57 // 3 SIGTERM/INT signals received; force exit without cleanup 58 logger.Info("Forcing docker daemon shutdown without cleanup; 3 interrupts received") 59 } 60 case syscall.SIGQUIT: 61 DumpStacks("") 62 logger.Info("Forcing docker daemon shutdown without cleanup on SIGQUIT") 63 } 64 // for the SIGINT/TERM, and SIGQUIT non-clean shutdown case, exit with 128 + signal # 65 os.Exit(128 + int(sig.(syscall.Signal))) 66 }(sig) 67 } 68 }() 69 } 70 71 const stacksLogNameTemplate = "goroutine-stacks-%s.log" 72 73 // DumpStacks appends the runtime stack into file in dir and returns full path 74 // to that file. 75 func DumpStacks(dir string) (string, error) { 76 var ( 77 buf []byte 78 stackSize int 79 ) 80 bufferLen := 16384 81 for stackSize == len(buf) { 82 buf = make([]byte, bufferLen) 83 stackSize = runtime.Stack(buf, true) 84 bufferLen *= 2 85 } 86 buf = buf[:stackSize] 87 var f *os.File 88 if dir != "" { 89 path := filepath.Join(dir, fmt.Sprintf(stacksLogNameTemplate, strings.Replace(time.Now().Format(time.RFC3339), ":", "", -1))) 90 var err error 91 f, err = os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0666) 92 if err != nil { 93 return "", errors.Wrap(err, "failed to open file to write the goroutine stacks") 94 } 95 defer f.Close() 96 defer f.Sync() 97 } else { 98 f = os.Stderr 99 } 100 if _, err := f.Write(buf); err != nil { 101 return "", errors.Wrap(err, "failed to write goroutine stacks") 102 } 103 return f.Name(), nil 104 }