-
Notifications
You must be signed in to change notification settings - Fork 41
/
managed_process_unix.go
117 lines (105 loc) · 3.21 KB
/
managed_process_unix.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
//go:build linux || darwin
package pexec
import (
"os"
"syscall"
"time"
"github.com/pkg/errors"
)
func sigStr(sig syscall.Signal) string {
//nolint:exhaustive
switch sig {
case syscall.SIGHUP:
return "SIGHUP"
case syscall.SIGINT:
return "SIGINT"
case syscall.SIGQUIT:
return "SIGQUIT"
case syscall.SIGABRT:
return "SIGABRT"
case syscall.SIGUSR1:
return "SIGUSR1"
case syscall.SIGUSR2:
return "SIGUSR2"
case syscall.SIGTERM:
return "SIGTERM"
default:
return "<UNKNOWN>"
}
}
var knownSignals = []syscall.Signal{
syscall.SIGHUP,
syscall.SIGINT,
syscall.SIGQUIT,
syscall.SIGABRT,
syscall.SIGUSR1,
syscall.SIGUSR2,
syscall.SIGTERM,
}
func parseSignal(sigStr, name string) (syscall.Signal, error) {
switch sigStr {
case "":
return 0, nil
case "HUP", "SIGHUP", "hangup", "1":
return syscall.SIGHUP, nil
case "INT", "SIGINT", "interrupt", "2":
return syscall.SIGINT, nil
case "QUIT", "SIGQUIT", "quit", "3":
return syscall.SIGQUIT, nil
case "ABRT", "SIGABRT", "aborted", "abort", "6":
return syscall.SIGABRT, nil
case "KILL", "SIGKILL", "killed", "kill", "9":
return syscall.SIGKILL, nil
case "TERM", "SIGTERM", "terminated", "terminate", "15":
return syscall.SIGTERM, nil
default:
return 0, errors.Errorf("unknown %q name", sigStr)
}
}
func sysProcAttr() *syscall.SysProcAttr {
return &syscall.SysProcAttr{Setpgid: true}
}
func (p *managedProcess) kill() (bool, error) {
p.logger.Infof("stopping process %d with signal %s", p.cmd.Process.Pid, p.stopSig)
// First let's try to directly signal the process.
if err := p.cmd.Process.Signal(p.stopSig); err != nil && !errors.Is(err, os.ErrProcessDone) {
return false, errors.Wrapf(err, "error signaling process %d with signal %s", p.cmd.Process.Pid, p.stopSig)
}
// In case the process didn't stop, or left behind any orphan children in its process group,
// we now send a signal to everything in the process group after a brief wait.
timer := time.NewTimer(p.stopWaitInterval)
defer timer.Stop()
select {
case <-timer.C:
p.logger.Infof("stopping entire process group %d with signal %s", p.cmd.Process.Pid, p.stopSig)
if err := syscall.Kill(-p.cmd.Process.Pid, p.stopSig); err != nil && !errors.Is(err, os.ErrProcessDone) {
return false, errors.Wrapf(err, "error signaling process group %d with signal %s", p.cmd.Process.Pid, p.stopSig)
}
case <-p.managingCh:
timer.Stop()
}
// Lastly, kill everything in the process group that remains after a longer wait
var forceKilled bool
timer2 := time.NewTimer(p.stopWaitInterval * 2)
defer timer2.Stop()
select {
case <-timer2.C:
p.logger.Infof("killing entire process group %d", p.cmd.Process.Pid)
if err := syscall.Kill(-p.cmd.Process.Pid, syscall.SIGKILL); err != nil && !errors.Is(err, os.ErrProcessDone) {
return false, errors.Wrapf(err, "error killing process group %d", p.cmd.Process.Pid)
}
forceKilled = true
case <-p.managingCh:
timer2.Stop()
}
return forceKilled, nil
}
func isWaitErrUnknown(err string, forceKilled bool) bool {
// This can easily happen if the process does not handle interrupts gracefully
// and it won't provide us any exit code info.
switch err {
case "signal: interrupt", "signal: terminated", "signal: killed":
return true
}
return false
}