github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/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 errorf Errorf 23 bin string 24 args []string 25 closing chan bool 26 } 27 28 type Errorf func(msg string, args ...interface{}) 29 30 // NewManagerCmd starts new syz-manager process. 31 // name - name for logging. 32 // log - manager log file with stdout/stderr. 33 // bin/args - process binary/args. 34 func NewManagerCmd(name, log string, errorf Errorf, bin string, args ...string) *ManagerCmd { 35 mc := &ManagerCmd{ 36 name: name, 37 log: log, 38 errorf: errorf, 39 bin: bin, 40 args: args, 41 closing: make(chan bool), 42 } 43 go mc.loop() 44 return mc 45 } 46 47 // Close gracefully shutdowns the process and waits for its termination. 48 func (mc *ManagerCmd) Close() { 49 mc.closing <- true 50 <-mc.closing 51 } 52 53 func (mc *ManagerCmd) loop() { 54 const ( 55 restartPeriod = 10 * time.Minute // don't restart crashing manager more frequently than that 56 interruptTimeout = time.Minute // give manager that much time to react to SIGINT 57 ) 58 var ( 59 cmd *exec.Cmd 60 started time.Time 61 interrupted time.Time 62 stopped = make(chan error, 1) 63 closing = mc.closing 64 ticker1 = time.NewTicker(restartPeriod) 65 ticker2 = time.NewTicker(interruptTimeout) 66 ) 67 defer func() { 68 ticker1.Stop() 69 ticker2.Stop() 70 }() 71 for closing != nil || cmd != nil { 72 if cmd == nil { 73 // The command is not running. 74 // Don't restart too frequently (in case it instantly exits with an error). 75 if time.Since(started) > restartPeriod { 76 started = time.Now() 77 osutil.Rename(mc.log, mc.log+".old") 78 logfile, err := os.Create(mc.log) 79 if err != nil { 80 mc.errorf("failed to create manager log: %v", err) 81 } else { 82 cmd = osutil.Command(mc.bin, mc.args...) 83 cmd.Stdout = logfile 84 cmd.Stderr = logfile 85 err := cmd.Start() 86 logfile.Close() 87 if err != nil { 88 mc.errorf("failed to start manager: %v", err) 89 cmd = nil 90 } else { 91 log.Logf(1, "%v: started manager", mc.name) 92 go func() { 93 stopped <- cmd.Wait() 94 }() 95 } 96 } 97 } 98 } else { 99 // The command is running. Check if we need to kill it. 100 if closing == nil && time.Since(interrupted) > interruptTimeout { 101 log.Logf(1, "%v: killing manager", mc.name) 102 cmd.Process.Kill() 103 interrupted = time.Now() 104 } 105 } 106 107 select { 108 case <-closing: 109 closing = nil 110 if cmd != nil { 111 log.Logf(1, "%v: stopping manager", mc.name) 112 cmd.Process.Signal(syscall.SIGINT) 113 interrupted = time.Now() 114 } 115 case err := <-stopped: 116 if cmd == nil { 117 mc.errorf("spurious stop signal: %v", err) 118 } 119 if closing != nil { 120 mc.errorf("manager exited unexpectedly: %v", err) 121 } 122 cmd = nil 123 log.Logf(1, "%v: manager exited with %v", mc.name, err) 124 case <-ticker1.C: 125 case <-ticker2.C: 126 } 127 } 128 close(mc.closing) 129 }