-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathautorestart.go
107 lines (96 loc) · 2.87 KB
/
autorestart.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
package autorestart
import (
"log"
"os"
"os/exec"
"os/signal"
"path/filepath"
"syscall"
"time"
"github.com/tillberg/watcher"
)
func logf(format string, args ...interface{}) {
log.Printf("[autorestart] "+format+"\n", args...)
}
const errorPath = "*error*"
var _exePath = errorPath
func getExePath() string {
var err error
if _exePath == errorPath {
_exePath, err = exec.LookPath(os.Args[0])
if err != nil {
logf("Failed to resolve path to current program: %s", err)
_exePath = errorPath
} else {
_exePath, err = filepath.Abs(_exePath)
if err != nil {
logf("Failed to resolve absolute path to current program: %s", err)
_exePath = errorPath
} else {
_exePath = filepath.Clean(_exePath)
}
}
}
return _exePath
}
// Restart the current program when the program's executable is updated.
// This function is a wrapper around NotifyOnChange and RestartViaExec, calling the
// latter when the former signals that a change was detected.
func RestartOnChange() {
notifyChan := NotifyOnChange(true)
<-notifyChan
logf("%s changed. Restarting via exec.", getExePath())
// Sort of a maybe-workaround for the issue detailed in RestartViaExec:
time.Sleep(1 * time.Millisecond)
RestartViaExec()
}
// Subscribe to a notification when the current process' executable file is modified.
// Returns a channel to which notifications (just `true`) will be sent whenever a
// change is detected.
func NotifyOnChange(usePolling bool) chan bool {
notifyChan := make(chan bool)
go func() {
exePath := getExePath()
if exePath == errorPath {
return
}
notify, err := watcher.WatchExecutable(exePath, usePolling)
if err != nil {
logf("Failed to initialize watcher: %v", err)
return
}
for range notify {
notifyChan <- true
}
}()
return notifyChan
}
// Restart the current process by calling syscall.Exec, using os.Args (with filepath.LookPath)
// and os.Environ() to recreate the same args & environment that was used when the process was
// originally started.
// Due to using syscall.Exec, this function is not portable to systems that don't support exec.
func RestartViaExec() {
exePath := getExePath()
if exePath == errorPath {
return
}
for {
err := syscall.Exec(exePath, os.Args, os.Environ())
// Not sure if this is due to user error, a Go regression in 1.5.x, or arch something,
// but this started failing when called immediately; a short delay (perhaps to switch
// to a different thread? or maybe to actually delay for some reason?) seems to work
// all the time. though.
logf("syscall.Exec failed [%v], trying again in one second...", err)
time.Sleep(1 * time.Second)
}
}
func NotifyOnSighup() chan os.Signal {
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGHUP)
return sigChan
}
func NotifyOnSigterm() chan os.Signal {
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGTERM)
return sigChan
}