-
Notifications
You must be signed in to change notification settings - Fork 2
/
managercmd.go
129 lines (120 loc) · 3.16 KB
/
managercmd.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
// Copyright 2017 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
package main
import (
"os"
"os/exec"
"syscall"
"time"
"github.com/google/syzkaller/pkg/log"
"github.com/google/syzkaller/pkg/osutil"
)
// ManagerCmd encapsulates a single instance of syz-manager process.
// It automatically restarts syz-manager if it exits unexpectedly,
// and supports graceful shutdown via SIGINT.
type ManagerCmd struct {
name string
log string
errorf Errorf
bin string
args []string
closing chan bool
}
type Errorf func(msg string, args ...interface{})
// NewManagerCmd starts new syz-manager process.
// name - name for logging.
// log - manager log file with stdout/stderr.
// bin/args - process binary/args.
func NewManagerCmd(name, log string, errorf Errorf, bin string, args ...string) *ManagerCmd {
mc := &ManagerCmd{
name: name,
log: log,
errorf: errorf,
bin: bin,
args: args,
closing: make(chan bool),
}
go mc.loop()
return mc
}
// Close gracefully shutdowns the process and waits for its termination.
func (mc *ManagerCmd) Close() {
mc.closing <- true
<-mc.closing
}
func (mc *ManagerCmd) loop() {
const (
restartPeriod = 10 * time.Minute // don't restart crashing manager more frequently than that
interruptTimeout = time.Minute // give manager that much time to react to SIGINT
)
var (
cmd *exec.Cmd
started time.Time
interrupted time.Time
stopped = make(chan error, 1)
closing = mc.closing
ticker1 = time.NewTicker(restartPeriod)
ticker2 = time.NewTicker(interruptTimeout)
)
defer func() {
ticker1.Stop()
ticker2.Stop()
}()
for closing != nil || cmd != nil {
if cmd == nil {
// cmd is not running
// don't restart too frequently (in case it instantly exits with an error)
if time.Since(started) > restartPeriod {
started = time.Now()
osutil.Rename(mc.log, mc.log+".old")
logfile, err := os.Create(mc.log)
if err != nil {
mc.errorf("failed to create manager log: %v", err)
} else {
cmd = osutil.Command(mc.bin, mc.args...)
cmd.Stdout = logfile
cmd.Stderr = logfile
err := cmd.Start()
logfile.Close()
if err != nil {
mc.errorf("failed to start manager: %v", err)
cmd = nil
} else {
log.Logf(1, "%v: started manager", mc.name)
go func() {
stopped <- cmd.Wait()
}()
}
}
}
} else {
// cmd is running
if closing == nil && time.Since(interrupted) > interruptTimeout {
log.Logf(1, "%v: killing manager", mc.name)
cmd.Process.Kill()
interrupted = time.Now()
}
}
select {
case <-closing:
closing = nil
if cmd != nil {
log.Logf(1, "%v: stopping manager", mc.name)
cmd.Process.Signal(syscall.SIGINT)
interrupted = time.Now()
}
case err := <-stopped:
if cmd == nil {
mc.errorf("spurious stop signal: %v", err)
}
if closing != nil {
mc.errorf("manager exited unexpectedly: %v", err)
}
cmd = nil
log.Logf(1, "%v: manager exited with %v", mc.name, err)
case <-ticker1.C:
case <-ticker2.C:
}
}
close(mc.closing)
}