github.com/brahmaroutu/docker@v1.2.1-0.20160809185609-eb28dde01f16/pkg/signal/trap.go (about) 1 package signal 2 3 import ( 4 "os" 5 gosignal "os/signal" 6 "path/filepath" 7 "runtime" 8 "sync/atomic" 9 "syscall" 10 "time" 11 12 "github.com/Sirupsen/logrus" 13 ) 14 15 // Trap sets up a simplified signal "trap", appropriate for common 16 // behavior expected from a vanilla unix command-line tool in general 17 // (and the Docker engine in particular). 18 // 19 // * If SIGINT or SIGTERM are received, `cleanup` is called, then the process is terminated. 20 // * If SIGINT or SIGTERM are received 3 times before cleanup is complete, then cleanup is 21 // skipped and the process is terminated immediately (allows force quit of stuck daemon) 22 // * A SIGQUIT always causes an exit without cleanup, with a goroutine dump preceding exit. 23 // * Ignore SIGPIPE events. These are generated by systemd when journald is restarted while 24 // the docker daemon is not restarted and also running under systemd. 25 // Fixes https://github.com/docker/docker/issues/19728 26 // 27 func Trap(cleanup func()) { 28 c := make(chan os.Signal, 1) 29 // we will handle INT, TERM, QUIT, SIGPIPE here 30 signals := []os.Signal{os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGPIPE} 31 gosignal.Notify(c, signals...) 32 go func() { 33 interruptCount := uint32(0) 34 for sig := range c { 35 if sig == syscall.SIGPIPE { 36 continue 37 } 38 39 go func(sig os.Signal) { 40 logrus.Infof("Processing signal '%v'", sig) 41 switch sig { 42 case os.Interrupt, syscall.SIGTERM: 43 if atomic.LoadUint32(&interruptCount) < 3 { 44 // Initiate the cleanup only once 45 if atomic.AddUint32(&interruptCount, 1) == 1 { 46 // Call the provided cleanup handler 47 cleanup() 48 os.Exit(0) 49 } else { 50 return 51 } 52 } else { 53 // 3 SIGTERM/INT signals received; force exit without cleanup 54 logrus.Info("Forcing docker daemon shutdown without cleanup; 3 interrupts received") 55 } 56 case syscall.SIGQUIT: 57 DumpStacks("") 58 logrus.Info("Forcing docker daemon shutdown without cleanup on SIGQUIT") 59 } 60 //for the SIGINT/TERM, and SIGQUIT non-clean shutdown case, exit with 128 + signal # 61 os.Exit(128 + int(sig.(syscall.Signal))) 62 }(sig) 63 } 64 }() 65 } 66 67 // DumpStacks dumps the runtime stack. 68 func DumpStacks(root string) { 69 var ( 70 buf []byte 71 stackSize int 72 ) 73 bufferLen := 16384 74 for stackSize == len(buf) { 75 buf = make([]byte, bufferLen) 76 stackSize = runtime.Stack(buf, true) 77 bufferLen *= 2 78 } 79 buf = buf[:stackSize] 80 // Note that if the daemon is started with a less-verbose log-level than "info" (the default), the goroutine 81 // traces won't show up in the log. 82 if root == "" { 83 logrus.Infof("=== BEGIN goroutine stack dump ===\n%s\n=== END goroutine stack dump ===", buf) 84 } else { 85 // Dumps the stacks to a file in the root directory of the daemon 86 // On Windows, this overcomes two issues - one being that if the stack is too big, it doesn't 87 // get written to the event log when the Windows daemon is running as a service. 88 // Second, using logrus, the tabs and new-lines end up getting written as literal 89 // \t and \n's, meaning you need to use something like notepad++ to convert the 90 // output into something readable using 'type' from a command line or notepad/notepad++ etc. 91 path := filepath.Join(root, "goroutine-stacks.log") 92 f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666) 93 if err != nil { 94 logrus.Warnf("Could not open %s to write the goroutine stacks: %v", path, err) 95 return 96 } 97 defer f.Close() 98 f.WriteString("=== BEGIN goroutine stack dump ===\n") 99 f.WriteString(time.Now().String() + "\n") 100 if _, err := f.Write(buf); err != nil { 101 logrus.Warnf("Could not write goroutine stacks to %s: %v", path, err) 102 return 103 } 104 f.WriteString("=== END goroutine stack dump ===\n") 105 f.Sync() 106 logrus.Infof("goroutine stacks written to %s", path) 107 } 108 }