-
Notifications
You must be signed in to change notification settings - Fork 29
/
Copy pathgow_cmd.go
114 lines (97 loc) · 2.32 KB
/
gow_cmd.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
package main
import (
"os"
"os/exec"
"sync/atomic"
"syscall"
"time"
"github.com/mitranim/gg"
)
type Cmd struct {
Mained
Count atomic.Int64
}
func (self *Cmd) Deinit() {
if self.Count.Load() > 0 {
self.Broadcast(syscall.SIGTERM)
}
}
/*
Note: proc count may change immediately after the call. Decision making at the
callsite must account for this.
*/
func (self *Cmd) IsRunning() bool { return self.Count.Load() == 0 }
func (self *Cmd) Restart() {
self.Deinit()
main := self.Main()
opt := main.Opt
cmd := exec.Command(opt.Cmd, opt.Args...)
if !main.Term.IsActive() {
cmd.Stdin = os.Stdin
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Start()
if err != nil {
log.Println(`unable to start subcommand:`, err)
return
}
self.Count.Add(1)
go self.ReportCmd(cmd, time.Now())
}
func (self *Cmd) ReportCmd(cmd *exec.Cmd, start time.Time) {
defer self.Count.Add(-1)
opt := self.Main().Opt
opt.LogCmdExit(cmd.Wait(), time.Since(start))
opt.TermSuf()
}
/*
Sends the signal to all subprocesses (descendants included).
Worth mentioning: across all the various Go versions tested (1.11 to 1.24), it
seemed that the `go` commands such as `go run` or `go test` do not forward any
interrupt or kill signals to its subprocess, and neither does `go test`. For
us, this means that terminating the immediate child process is worth very
little; we're concerned with terminating the grand-child processes, which may
be spawned by the common cases `go run`, `go test`, or any replacement commands
from `Opt.Cmd`.
*/
func (self *Cmd) Broadcast(sig syscall.Signal) {
verb := self.Main().Opt.Verb
pids, err := SubPids(os.Getpid(), verb)
if err != nil {
log.Println(err)
return
}
if gg.IsEmpty(pids) {
return
}
if !verb {
for _, pid := range pids {
gg.Nop1(syscall.Kill(pid, sig))
return
}
}
var sent []int
var unsent []int
var errs []error
for _, pid := range pids {
err := syscall.Kill(pid, sig)
if err != nil {
unsent = append(unsent, pid)
errs = append(errs, err)
} else {
sent = append(sent, pid)
}
}
if gg.IsEmpty(errs) {
log.Printf(
`sent signal %q to %v subprocesses, pids: %v`,
sig, len(pids), sent,
)
} else {
log.Printf(
`tried to send signal %q to %v subprocesses, sent to pids: %v, not sent to pids: %v, errors: %q`,
sig, len(pids), sent, unsent, errs,
)
}
}