-
Notifications
You must be signed in to change notification settings - Fork 0
/
watchdog.go
153 lines (132 loc) · 3.59 KB
/
watchdog.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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
/*
//NOTES:
//When using this watchdog package, it is important that you use
// the imported pflag package from spf13 on GitHub to define extra args.
// This is how flags are currently processed, and is not expected to
// change. Wrappers are planned but too bloated for now.
//It should also be expected that the watchdog will panic if it cannot
// spawn a new main process, so please use recover() accordingly.
*/
package watchdog
import (
"fmt"
"os"
"os/exec"
"os/signal"
"path/filepath"
"syscall"
"time"
"github.com/mitchellh/go-ps"
"github.com/spf13/pflag"
)
var (
Delay time.Duration = (time.Second * 5) //How often to check if the main process is alive
Footer string //Displayed when program exits
Header string //Displayed when program starts
ImmediateSpawn bool = true //If the main process should be spawned immediately after writing the Header
KillOldMain bool //Set to true if this should be the only existing watchdog parent, killing dangling processes with a matching os.Args[0]
Signals []syscall.Signal = []syscall.Signal{syscall.SIGINT, syscall.SIGKILL}
isMain bool
mainArgs []string
mainPID int = -1
watchdogPID int = -1
)
//Parse returns false if it has to block and act as a watchdog until exit, or returns true if code execution should continue
func Parse() bool {
pflag.BoolVar(&isMain, "isMain", false, "act as the main process instead of the watchdog process")
pflag.IntVar(&watchdogPID, "watchdogPID", -1, "used as the exception when killing old main processes")
pflag.ParseAll(parseFlag)
pflag.Parse()
if isMain {
return true
}
if Header != "" {
fmt.Println(Header)
}
sc := make(chan os.Signal, 1)
for i := 0; i < len(Signals); i++ {
signal.Notify(sc, Signals[i])
}
if ImmediateSpawn {
mainPID = spawnMain()
}
watchdogTicker := time.Tick(Delay)
for {
exitWatchdog := false
select {
case <-watchdogTicker:
if !isProcessRunning(mainPID) {
mainPID = spawnMain()
}
case _, ok := <-sc:
if ok {
if isProcessRunning(mainPID) {
mainProc, err := os.FindProcess(mainPID)
if err == nil {
_ = mainProc.Signal(syscall.SIGINT)
waitProcess(mainPID)
}
}
exitWatchdog = true
break
}
}
if exitWatchdog {
break
}
}
if Footer != "" {
fmt.Println(Footer)
}
return false
}
func parseFlag(flag *pflag.Flag, value string) error {
//We don't want to append self-defined args to mainCmd
if flag.Name != "isMain" && flag.Name != "watchdogPID" {
mainArgs = append(mainArgs, []string{"--" + flag.Name, value}...)
}
return nil
}
func spawnMain() int {
if KillOldMain {
processList, err := ps.Processes()
if err == nil {
for _, process := range processList {
if process.Pid() != os.Getpid() && process.Pid() != watchdogPID && process.Executable() == filepath.Base(os.Args[0]) {
oldProcess, err := os.FindProcess(process.Pid())
if oldProcess != nil && err == nil {
oldProcess.Signal(syscall.SIGKILL)
}
}
}
}
}
mainCmdArgs := []string{"--isMain", "--watchdogPID", fmt.Sprintf("%d", os.Getpid())}
mainCmdArgs = append(mainCmdArgs, mainArgs...)
mainCmd := exec.Command(os.Args[0], mainCmdArgs...)
mainCmd.Stdout = os.Stdout
mainCmd.Stderr = os.Stderr
mainCmd.Stdin = os.Stdin
err := mainCmd.Start()
if err != nil {
panic("watchdog: failed to start new main process")
}
return mainCmd.Process.Pid
}
func isProcessRunning(pid int) bool {
if pid < 0 {
return false
}
process, err := ps.FindProcess(pid)
if err != nil {
return false
}
return process != nil
}
func waitProcess(pid int) {
process, err := os.FindProcess(pid)
if err != nil {
return
}
_, _ = process.Wait()
}