1package signal // import "github.com/docker/docker/pkg/signal"
2
3import (
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/docker/docker/issues/19728
28//
29func 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
71const stacksLogNameTemplate = "goroutine-stacks-%s.log"
72
73// DumpStacks appends the runtime stack into file in dir and returns full path
74// to that file.
75func 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}
105