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  }