github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/syz-ci/managercmd.go (about) 1 // Copyright 2017 syzkaller project authors. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 package main 5 6 import ( 7 "os" 8 "os/exec" 9 "syscall" 10 "time" 11 12 "github.com/google/syzkaller/pkg/log" 13 "github.com/google/syzkaller/pkg/osutil" 14 ) 15 16 // ManagerCmd encapsulates a single instance of syz-manager process. 17 // It automatically restarts syz-manager if it exits unexpectedly, 18 // and supports graceful shutdown via SIGINT. 19 type ManagerCmd struct { 20 name string 21 log string 22 bench string 23 errorf Errorf 24 bin string 25 args []string 26 closing chan bool 27 } 28 29 type Errorf func(msg string, args ...interface{}) 30 31 // NewManagerCmd starts new syz-manager process. 32 // name - name for logging. 33 // log - manager log file with stdout/stderr. 34 // bin/args - process binary/args. 35 func NewManagerCmd(name, log, bench string, errorf Errorf, bin string, args ...string) *ManagerCmd { 36 mc := &ManagerCmd{ 37 name: name, 38 log: log, 39 bench: bench, 40 errorf: errorf, 41 bin: bin, 42 args: args, 43 closing: make(chan bool), 44 } 45 go mc.loop() 46 return mc 47 } 48 49 // Close gracefully shutdowns the process and waits for its termination. 50 func (mc *ManagerCmd) Close() { 51 mc.closing <- true 52 <-mc.closing 53 } 54 55 func (mc *ManagerCmd) loop() { 56 const ( 57 restartPeriod = 10 * time.Minute // don't restart crashing manager more frequently than that 58 interruptTimeout = time.Minute // give manager that much time to react to SIGINT 59 ) 60 var ( 61 cmd *exec.Cmd 62 started time.Time 63 interrupted time.Time 64 stopped = make(chan error, 1) 65 closing = mc.closing 66 ticker1 = time.NewTicker(restartPeriod) 67 ticker2 = time.NewTicker(interruptTimeout) 68 ) 69 defer func() { 70 ticker1.Stop() 71 ticker2.Stop() 72 }() 73 for closing != nil || cmd != nil { 74 if cmd == nil { 75 // The command is not running. 76 // Don't restart too frequently (in case it instantly exits with an error). 77 if time.Since(started) > restartPeriod { 78 started = time.Now() 79 osutil.Rename(mc.log, mc.log+".old") 80 osutil.Rename(mc.bench, mc.bench+".old") // or else syz-manager will complain 81 logfile, err := os.Create(mc.log) 82 if err != nil { 83 mc.errorf("failed to create manager log: %v", err) 84 } else { 85 cmd = osutil.Command(mc.bin, mc.args...) 86 cmd.Stdout = logfile 87 cmd.Stderr = logfile 88 err := cmd.Start() 89 logfile.Close() 90 if err != nil { 91 mc.errorf("failed to start manager: %v", err) 92 cmd = nil 93 } else { 94 log.Logf(1, "%v: started manager", mc.name) 95 go func() { 96 stopped <- cmd.Wait() 97 }() 98 } 99 } 100 } 101 } else { 102 // The command is running. Check if we need to kill it. 103 if closing == nil && time.Since(interrupted) > interruptTimeout { 104 log.Logf(1, "%v: killing manager", mc.name) 105 cmd.Process.Kill() 106 interrupted = time.Now() 107 } 108 } 109 110 select { 111 case <-closing: 112 closing = nil 113 if cmd != nil { 114 log.Logf(1, "%v: stopping manager", mc.name) 115 cmd.Process.Signal(syscall.SIGINT) 116 interrupted = time.Now() 117 } 118 case err := <-stopped: 119 if cmd == nil { 120 mc.errorf("spurious stop signal: %v", err) 121 } 122 if closing != nil { 123 mc.errorf("manager exited unexpectedly: %v", err) 124 } 125 cmd = nil 126 log.Logf(1, "%v: manager exited with %v", mc.name, err) 127 case <-ticker1.C: 128 case <-ticker2.C: 129 } 130 } 131 close(mc.closing) 132 }