-
Notifications
You must be signed in to change notification settings - Fork 0
/
reload.go
213 lines (186 loc) · 4.34 KB
/
reload.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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
//go:build !windows
// +build !windows
package ibeamcorelib
import (
"fmt"
"io"
"os"
"os/exec"
"os/signal"
"syscall"
log "github.com/s00500/env_logger"
)
// The Double-exec strategy: parent forks child to exec (first) with an
// inherited net.Listener; child signals parent to exec (second); parent
// kills child.
const (
SIGINT = syscall.SIGINT
SIGQUIT = syscall.SIGQUIT
SIGTERM = syscall.SIGTERM
SIGUSR2 = syscall.SIGUSR2
)
// Re-exec this same image
func execReload() error {
var pid int
fmt.Sscan(os.Getenv("CORERELOAD_PID"), &pid)
if syscall.Getppid() == pid {
return fmt.Errorf("reload.Exec called by a child process")
}
argv0, err := lookPath()
if nil != err {
return err
}
if err := os.Setenv("CORERELOAD_SIGNAL", fmt.Sprintf("%d", syscall.SIGTERM)); nil != err {
return err
}
if err := os.Setenv("CORERELOAD_CHILD", "0"); nil != err {
return err
}
log.Traceln("re-executing", argv0)
return syscall.Exec(argv0, os.Args, os.Environ())
}
// Fork and exec this same image
func forkExec() error {
argv0, err := lookPath()
if nil != err {
return err
}
wd, err := os.Getwd()
if nil != err {
return err
}
if err := os.Setenv("CORERELOAD_PID", ""); nil != err {
return err
}
if err := os.Setenv(
"CORERELOAD_PPID",
fmt.Sprint(syscall.Getpid()),
); nil != err {
return err
}
sig := syscall.SIGUSR2
if err := os.Setenv("CORERELOAD_SIGNAL", fmt.Sprintf("%d", sig)); nil != err {
return err
}
if err := os.Setenv("CORERELOAD_CHILD", "1"); nil != err {
return err
}
files := make([]*os.File, 3)
files[syscall.Stdin] = os.Stdin
files[syscall.Stdout] = os.Stdout
files[syscall.Stderr] = os.Stderr
p, err := os.StartProcess(argv0, os.Args, &os.ProcAttr{
Dir: wd,
Env: os.Environ(),
Files: files,
Sys: &syscall.SysProcAttr{},
})
if nil != err {
return err
}
log.Debugln("spawned child", p.Pid)
if err = os.Setenv("CORERELOAD_PID", fmt.Sprint(p.Pid)); nil != err {
return err
}
return nil
}
// kill process specified in the environment with the signal specified in the
// environment; default to SIGQUIT.
func kill() error {
var (
pid int
sig syscall.Signal
)
_, err := fmt.Sscan(os.Getenv("CORERELOAD_PID"), &pid)
if err == io.EOF {
_, err = fmt.Sscan(os.Getenv("CORERELOAD_PPID"), &pid)
}
if err != nil {
return err
}
if _, err := fmt.Sscan(os.Getenv("CORERELOAD_SIGNAL"), &sig); nil != err {
sig = syscall.SIGTERM
}
if syscall.SIGTERM == sig {
go syscall.Wait4(pid, nil, 0, nil)
}
log.Traceln("sending signal", sig, "to process", pid)
return syscall.Kill(pid, sig)
}
func ReloadHook() {
if isChild() {
log.Info("Reloading Core...")
log.Trace("is childprocess, kill parent")
err := kill()
log.MustFatal(err)
os.Exit(0)
}
}
func isChild() bool {
return os.Getenv("CORERELOAD_CHILD") == "1"
}
// Block this goroutine awaiting signals. Signals are handled as they
// are by Nginx and Unicorn: <http://unicorn.bogomips.org/SIGNALS.html>.
func wait() (syscall.Signal, error) {
ch := make(chan os.Signal, 2)
signal.Notify(
ch,
syscall.SIGHUP,
syscall.SIGINT,
//syscall.SIGQUIT,
syscall.SIGTERM,
syscall.SIGUSR1,
syscall.SIGUSR2,
)
forked := false
for {
sig := <-ch
switch sig {
// SIGHUP should reload configuration.
case syscall.SIGHUP:
// FIXME: make sure to foward all sigs to the caller ?
//if nil != OnSIGHUP {
// if err := OnSIGHUP(l); nil != err {
// log.Println("OnSIGHUP:", err)
// }
//}
// SIGINT should exit.
case syscall.SIGINT:
return syscall.SIGINT, nil
// SIGQUIT should exit gracefully.
case syscall.SIGQUIT:
return syscall.SIGQUIT, nil
// SIGTERM should exit.
case syscall.SIGTERM:
return syscall.SIGTERM, nil
// SIGUSR1 should reopen logs.
case syscall.SIGUSR1:
// FIXME: make sure to foward all sigs to the caller ?
//if nil != OnSIGUSR1 {
// if err := OnSIGUSR1(l); nil != err {
// log.Println("OnSIGUSR1:", err)
// }
//}
// SIGUSR2 forks and re-execs the first time it is received and execs
// without forking from then on.
case syscall.SIGUSR2:
if forked {
return syscall.SIGUSR2, nil
}
forked = true
if err := forkExec(); nil != err {
return syscall.SIGUSR2, err
}
}
}
}
func lookPath() (argv0 string, err error) {
argv0, err = exec.LookPath(os.Args[0])
if nil != err {
return
}
if _, err = os.Stat(argv0); nil != err {
return
}
return
}