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  }