-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathprocess_manager.go
91 lines (72 loc) · 2.2 KB
/
process_manager.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
package main
import (
"log"
"os"
"os/exec"
"strings"
"sync"
"syscall"
"time"
)
type ProcessManager struct {
mutex sync.Mutex
cmd *exec.Cmd
pgid int // process group ID. will be used to kill the entire process group
waitDone chan struct{} // channel to signal when Wait() completes
}
// runs a go run command
func (pm *ProcessManager) RunProcess(filename string, programArgs []string) {
pm.mutex.Lock()
defer pm.mutex.Unlock()
// create a new channel for this process
pm.waitDone = make(chan struct{})
command := []string{"go", "run", filename}
command = append(command, programArgs...)
log.Printf("Running `%s`", strings.Join(command, " "))
pm.cmd = exec.Command(command[0], command[1:]...)
pm.cmd.Stdin = os.Stdin
pm.cmd.Stdout = os.Stdout
pm.cmd.Stderr = os.Stderr
pm.cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true, // create a new process group
}
if err := pm.cmd.Start(); err != nil {
log.Printf("error starting process: %v", err)
return
}
// store the process group ID. will be used to kill the entire process group
pm.pgid = pm.cmd.Process.Pid
go func() {
err := pm.cmd.Wait()
if err == nil {
log.Println("clean exit - waiting for changes before restart")
} else {
log.Printf("app crashed: %v", err)
log.Println("waiting for changes before restart")
}
// signal that Wait() has completed by closing this channel
close(pm.waitDone)
}()
}
// stops the process
func (pm *ProcessManager) StopProcess() {
pm.mutex.Lock()
defer pm.mutex.Unlock()
if pm.cmd == nil || pm.cmd.Process == nil {
return
}
// kill the entire process group
pgidKill := -pm.pgid // negative pgid means kill process group
syscall.Kill(pgidKill, syscall.SIGTERM)
// give processes a moment to terminate gracefully
time.Sleep(100 * time.Millisecond)
// force kill if still running
syscall.Kill(pgidKill, syscall.SIGKILL)
// wait for the anon Wait() goroutine to finish
<-pm.waitDone
// if we were to set pm.cmd before reading from pm.waitDone, then it might
// cause a race condition because the Wait() goroutine uses pm.cmd.Wait()
// to wait for the process to exit. this is why we read from pm.waitDone
// first and then set pm.cmd to nil
pm.cmd = nil
}