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