-
Notifications
You must be signed in to change notification settings - Fork 0
/
readysignal.go
132 lines (119 loc) · 3.01 KB
/
readysignal.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
package main
import (
"fmt"
"os"
"os/exec"
"os/signal"
"path/filepath"
"strconv"
"strings"
"syscall"
)
// SignalListener has channels and methods required to watch signals.
type SignalListener struct {
recv chan os.Signal
stop chan bool
done chan bool
}
// NewSignalListener starts listening for a signal,
// and returns a Listener for management.
func NewSignalListener() *SignalListener {
return &SignalListener{
recv: make(chan os.Signal, 1),
stop: make(chan bool),
done: make(chan bool, 1),
}
}
// Listen starts listening for a signal type
// and returns the channel for receiving them.
func (l *SignalListener) Listen(sig os.Signal) chan os.Signal {
sigchan := make(chan os.Signal, 1)
signal.Notify(sigchan, sig)
go func() {
for {
select {
case sig := <-sigchan:
if sig == nil {
l.done <- true
return
}
l.recv <- sig
case <-l.stop:
signal.Stop(sigchan)
close(sigchan)
}
}
}()
return l.recv
}
// Stop will stop listening for the signal.
func (l *SignalListener) Stop() {
l.stop <- true
<-l.done
}
// ReceiveReadySignal listens for "ready" signals,
// and returns a channel for receiving them.
func ReceiveReadySignal() chan os.Signal {
l := NewSignalListener()
return l.Listen(syscall.SIGUSR1)
}
// SendReadySignal tries to send a "ready" signal
// to the ancestor Remake process, if there is one.
func SendReadySignal() (err error) {
processID := os.Getpid()
processName, err := getProcessName(processID)
if err != nil {
return fmt.Errorf("getProcessName %d: %s", processID, err)
}
// Search for an ancestor process with the same name as this one. In other
// words, find the original "remake" process that ran "remake -ready".
parentID := os.Getppid()
for {
if parentID == 0 {
return nil
}
name, err := getProcessName(parentID)
if err != nil {
return fmt.Errorf("getProcessName %d: %s", parentID, err)
}
if name == processName {
break
}
parentID, err = getParentID(parentID)
if err != nil {
return fmt.Errorf("getParentID: %d", parentID)
}
}
// The ancestor process has been found, so it can be signaled. That lets
// it know that the dependencies have been built, and it can proceed past
// the init stage and start monitoring for changes.
p, err := os.FindProcess(parentID)
if err != nil {
return err
}
if err := p.Signal(syscall.SIGUSR1); err != nil {
return fmt.Errorf("p.Signal: %s", err)
}
return nil
}
// getProcessName gets the base name of a process.
func getProcessName(pid int) (name string, err error) {
p := fmt.Sprintf("%d", pid)
cmd := exec.Command("ps", "-p", p, "-o", "comm=")
out, err := cmd.Output()
if err != nil {
return name, err
}
name = filepath.Base(string(out))
return name, nil
}
// getParentID gets the parent ID of a process.
func getParentID(pid int) (ppid int, err error) {
spid := fmt.Sprintf("%d", pid)
out, err := exec.Command("ps", "-p", spid, "-o", "ppid=").Output()
if err != nil {
return ppid, err
}
ppid, err = strconv.Atoi(strings.TrimSpace(string(out)))
return ppid, err
}