forked from zh-five/xdaemon
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdaemon.go
142 lines (123 loc) · 3.48 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
package xdaemon
import (
"fmt"
"log"
"os"
"os/exec"
"strconv"
"time"
)
const ENV_NAME = "XW_DAEMON_IDX"
//运行时调用background的次数
var runIdx int = 0
//守护进程
type Daemon struct {
LogFile string //日志文件, 记录守护进程和子进程的标准输出和错误输出. 若为空则不记录
MaxCount int //循环重启最大次数, 若为0则无限重启
MaxError int //连续启动失败或异常退出的最大次数, 超过此数, 守护进程退出, 不再重启子进程
MinExitTime int64 //子进程正常退出的最小时间(秒). 小于此时间则认为是异常退出
}
// 把本身程序转化为后台运行(启动一个子进程, 然后自己退出)
// logFile 若不为空,子程序的标准输出和错误输出将记入此文件
// isExit 启动子加进程后是否直接退出主程序, 若为false, 主程序返回*os.Process, 子程序返回 nil. 需自行判断处理
func Background(logFile string, isExit bool) (*exec.Cmd, error) {
//判断子进程还是父进程
runIdx++
envIdx, err := strconv.Atoi(os.Getenv(ENV_NAME))
if err != nil {
envIdx = 0
}
if runIdx <= envIdx { //子进程, 退出
return nil, nil
}
//设置子进程环境变量
env := os.Environ()
env = append(env, fmt.Sprintf("%s=%d", ENV_NAME, runIdx))
//启动子进程
cmd, err := startProc(os.Args, env, logFile)
if err != nil {
log.Println(os.Getpid(), "启动子进程失败:", err)
return nil, err
} else {
//执行成功
log.Println(os.Getpid(), ":", "启动子进程成功:", "->", cmd.Process.Pid, "\n ")
}
if isExit {
os.Exit(0)
}
return cmd, nil
}
func NewDaemon(logFile string) *Daemon {
return &Daemon{
LogFile: logFile,
MaxCount: 0,
MaxError: 3,
MinExitTime: 10,
}
}
// 启动后台守护进程
func (d *Daemon) Run() {
//启动一个守护进程后退出
Background(d.LogFile, true)
//守护进程启动一个子进程, 并循环监视
var t int64
count := 1
errNum := 0
for {
//daemon 信息描述
dInfo := fmt.Sprintf("守护进程(pid:%d; count:%d/%d; errNum:%d/%d):",
os.Getpid(), count, d.MaxCount, errNum, d.MaxError)
if errNum > d.MaxError {
log.Println(dInfo, "启动子进程失败次数太多,退出")
os.Exit(1)
}
if d.MaxCount > 0 && count > d.MaxCount {
log.Println(dInfo, "重启次数太多退出")
os.Exit(0)
}
count++
t = time.Now().Unix() //启动时间戳
cmd, err := Background(d.LogFile, false)
if err != nil { //启动失败
log.Println(dInfo, "子进程启动失败;", "err:", err)
errNum++
continue
}
//子进程,
if cmd == nil {
log.Printf("子进程pid=%d: 开始运行...", os.Getpid())
break
}
//父进程: 等待子进程退出
err = cmd.Wait()
dat := time.Now().Unix() - t //子进程运行秒数
if dat < d.MinExitTime { //异常退出
errNum++
} else { //正常退出
errNum = 0
}
log.Printf("%s 监视到子进程(%d)退出, 共运行了%d秒: %v\n", dInfo, cmd.ProcessState.Pid(), dat, err)
}
}
func startProc(args, env []string, logFile string) (*exec.Cmd, error) {
cmd := &exec.Cmd{
Path: args[0],
Args: args,
Env: env,
SysProcAttr: NewSysProcAttr(),
}
if logFile != "" {
stdout, err := os.OpenFile(logFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
if err != nil {
log.Println(os.Getpid(), ": 打开日志文件错误:", err)
return nil, err
}
cmd.Stderr = stdout
cmd.Stdout = stdout
}
err := cmd.Start()
if err != nil {
return nil, err
}
return cmd, nil
}