/
daemon.go
198 lines (165 loc) · 5.38 KB
/
daemon.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
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"strconv"
"strings"
"sync"
"syscall"
"time"
"github.com/hpcloud/tail"
)
// Service is the interface used to mock the Daemon structure.
type Service interface {
Start(stopNotifier chan<- string)
Stop()
Kill()
}
// Daemon is a program executed in the background.
// Command is the UNIX command that starts the Daemon.
// The program will be stopped with a SIGTERM signal,
// but if StopCommand is defined, it will be used to stop the Daemon instead.
// The program running in the background is Command except if PidFile is defined.
// In this case the program is the one that has the pid specified in the PidFile.
// If LogFiles is not empty, the files will be used to stream
// the logs of the Daemon in addition to the Command output.
// You should probably use LogFiles if PidFile is defined.
type Daemon struct {
Name string
Command []string `yaml:"command"`
StopCommand []string `yaml:"stopCommand"`
PidFile string `yaml:"pidFile"`
LogFiles []string `yaml:"logFiles"`
mu sync.Mutex
process *os.Process
}
// Start executes the Daemon Command.
// It sets the Daemon process that is either the command process
// or the process defined by the PidFile.
// It also streams the Daemon logs on the standard output.
// When the daemon dies or when its logs are not streamed anymore,
// it sends a message in the stopNotifier channel
// to report that the Daemon is not working properly anymore.
func (d *Daemon) Start(stopNotifier chan<- string) {
log.Printf("starting `%s` daemon with command : %s\n", d.Name, d.Command)
cmd := exec.Command(d.Command[0], d.Command[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
for _, logFile := range d.LogFiles {
go d.printLogs(logFile, stopNotifier)
}
if d.PidFile == "" {
d.startForeground(cmd, stopNotifier)
return
}
d.startBackground(cmd, stopNotifier)
}
func (d *Daemon) startForeground(cmd *exec.Cmd, stopNotifier chan<- string) {
if err := cmd.Start(); err != nil {
stopNotifier <- fmt.Sprintf("could not start daemon `%s` : %s", d.Name, err)
return
}
d.setProcess(cmd.Process)
// send notification when the process dies
go d.waitChild(stopNotifier)
}
func (d *Daemon) startBackground(cmd *exec.Cmd, stopNotifier chan<- string) {
// remove pidfile content to be sure to read the correct value
ioutil.WriteFile(d.PidFile, []byte{}, 0777)
cmd.Run()
pid := d.readPidFileRetry(500, 10*time.Millisecond)
process, _ := os.FindProcess(pid)
d.setProcess(process)
// send notification when the process dies
go d.wait(stopNotifier)
}
// waitChild waits for the Daemon process to finish and sends a message
// in the stopNotifier channel when that happens.
// The Daemon process need to be a child of this process.
func (d *Daemon) waitChild(stopNotifier chan<- string) {
if p := d.getProcess(); p != nil {
p.Wait()
}
stopNotifier <- fmt.Sprintf("daemon `%s` has stopped", d.Name)
}
// wait waits for the Daemon process to finish and sends a message
// in the stopNotifier channel when that happens.
// It checks every second that the process is still running.
func (d *Daemon) wait(stopNotifier chan<- string) {
if p := d.getProcess(); p != nil {
for isAlive(p.Pid) {
time.Sleep(time.Second)
}
}
stopNotifier <- fmt.Sprintf("daemon `%s` has stopped", d.Name)
}
// readPidFile returns the pid from the Daemon PidFile.
func (d *Daemon) readPidFile() (int, error) {
b, err := ioutil.ReadFile(d.PidFile)
if err != nil {
return 0, err
}
pid := strings.Trim(string(b), " \n")
return strconv.Atoi(pid)
}
// readPidFileRetry tries to read the pid from the Daemon PidFile.
// If it could not read it, it will retry maxAttempts times
// and will wait before each new attempt.
// If the pid file could not be red, it returns 0.
func (d *Daemon) readPidFileRetry(maxAttempts int, wait time.Duration) int {
for i := 0; i < maxAttempts; i++ {
if pid, _ := d.readPidFile(); pid != 0 {
return pid
}
}
return 0
}
// printLogs streams the content of a file on the standard output.
// When it stops working, it sends a message in the stopNotifier channel.
func (d *Daemon) printLogs(filename string, stopNotifier chan<- string) {
t, _ := tail.TailFile(filename, tail.Config{Follow: true})
for line := range t.Lines {
fmt.Println(line.Text)
}
stopNotifier <- fmt.Sprintf("could not stream logs from `%s`", filename)
}
// Stop tries to gracefully stop the Daemon.
// If StopCommand is defined, it executes the command.
// If not it sends a SIGTERM signal to the Daemon process.
func (d *Daemon) Stop() {
// there is a stop command to stop the process, execute it.
if len(d.StopCommand) > 0 {
cmd := exec.Command(d.StopCommand[0], d.StopCommand[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Run()
return
}
// there is no defined command to stop the process,
// just send a SIGTERM signal.
if p := d.getProcess(); len(d.StopCommand) == 0 && p != nil {
p.Signal(syscall.SIGTERM)
return
}
}
// Kill sends a SIGKILL signal to the Daemon process.
func (d *Daemon) Kill() {
if p := d.getProcess(); p != nil {
p.Signal(syscall.SIGKILL)
}
}
// getProcess is the thread safe process getter.
func (d *Daemon) getProcess() *os.Process {
d.mu.Lock()
defer d.mu.Unlock()
return d.process
}
// getProcess is the thread safe process setter.
func (d *Daemon) setProcess(process *os.Process) {
d.mu.Lock()
defer d.mu.Unlock()
d.process = process
}