github.com/m3db/m3@v1.5.0/src/x/panicmon/executor.go (about)

     1  // Copyright (c) 2017 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package panicmon
    22  
    23  import (
    24  	"errors"
    25  	"io"
    26  	"os"
    27  	"os/exec"
    28  	"os/signal"
    29  	"strconv"
    30  	"syscall"
    31  )
    32  
    33  // StatusCode represents the exit code of a process.
    34  type StatusCode int
    35  
    36  // String returns the string representation of the status code.
    37  func (r StatusCode) String() string {
    38  	return strconv.Itoa(int(r))
    39  }
    40  
    41  // An Executor is responsible for executing a command and forwarding signals
    42  // received by the parent to the spawned process. It reports process/signal
    43  // events via the ProcessHandler/SignalHandler interfaces it is created with.
    44  type Executor interface {
    45  	// Run executes a command as defined by args.
    46  	Run(args []string) (StatusCode, error)
    47  }
    48  
    49  // executor is the underlying implementation of Executor.
    50  type executor struct {
    51  	// channel on which os will send received signals
    52  	sigC chan os.Signal
    53  	// channel to shut down signal handler
    54  	closeC chan struct{}
    55  	// channel that will be closed when the executor has finished closing
    56  	closeDone chan struct{}
    57  
    58  	handler Handler
    59  	stdout  io.Writer
    60  	stderr  io.Writer
    61  	env     []string
    62  }
    63  
    64  // ExecutorOptions specifies options for creating a new executor.
    65  type ExecutorOptions struct {
    66  	// Handler for signal and process events.
    67  	Handler Handler
    68  
    69  	// If ListenSigs is non-empty than only the signals specified will be
    70  	// listened to / forwarded. Default (empty) will cause all signals to be
    71  	// listened to, and all signals except SIGCHLD will be forwarded to the
    72  	// child.
    73  	Signals []os.Signal
    74  
    75  	// By default panicmon directs the child's stdout/stderr to its own in order
    76  	// to seamlessly wrap the process. This behavior can be overridden by
    77  	// specifying an io.Writer to send the child's stdout/stderr to. If either
    78  	// is nil the parent's will be used. Output can be silenced by setting either
    79  	// to ioutil.Discard.
    80  	Stdout io.Writer
    81  	Stderr io.Writer
    82  
    83  	// By default panicmon uses the existing processes environment variables
    84  	// when launching child processes. Set Env if you'd like to specify you're
    85  	// own environment variables to spawn with, on how to use this option see
    86  	// os/exec.Cmd.
    87  	Env []string
    88  }
    89  
    90  // Validate ensures that an ExecutorOpts type is valid. Specifically, it will
    91  // set ProcessHandler and SignalHandler to noop handlers if either of them are
    92  // nil.
    93  func (opts *ExecutorOptions) Validate() {
    94  	if opts.Handler.ProcessHandler == nil {
    95  		opts.Handler.ProcessHandler = NoopProcessHandler{}
    96  	}
    97  
    98  	if opts.Handler.SignalHandler == nil {
    99  		opts.Handler.SignalHandler = NoopSignalHandler{}
   100  	}
   101  }
   102  
   103  // NewDefaultExecutorOpts returns an ExecutorOpts that will listen to / forward
   104  // all signals (except SIGCHLD) but will use noop handlers for signal/process
   105  // events.
   106  func NewDefaultExecutorOpts() ExecutorOptions {
   107  	return ExecutorOptions{
   108  		Handler: Handler{
   109  			ProcessHandler: NoopProcessHandler{},
   110  			SignalHandler:  NoopSignalHandler{},
   111  		},
   112  	}
   113  }
   114  
   115  // NewExecutor returns an executor configured according to opts (after calling
   116  // opts.Validate()).
   117  func NewExecutor(opts ExecutorOptions) Executor {
   118  	opts.Validate()
   119  
   120  	ex := &executor{
   121  		sigC:      make(chan os.Signal, 2),
   122  		closeC:    make(chan struct{}),
   123  		closeDone: make(chan struct{}),
   124  		handler:   opts.Handler,
   125  		stdout:    opts.Stdout,
   126  		stderr:    opts.Stderr,
   127  		env:       opts.Env,
   128  	}
   129  
   130  	signal.Notify(ex.sigC, opts.Signals...)
   131  
   132  	return ex
   133  }
   134  
   135  func (ex *executor) Run(args []string) (StatusCode, error) {
   136  	ex.handler.ProcessStarted(ProcessStartEvent{Args: args})
   137  
   138  	result, err := ex.execCmd(args)
   139  	if result.failedAtStartup && err != nil {
   140  		ex.handler.ProcessFailed(ProcessFailedEvent{
   141  			Args: args,
   142  			Err:  err,
   143  		})
   144  		return result.statusCode, err
   145  	}
   146  
   147  	ex.handler.ProcessExited(ProcessExitedEvent{
   148  		Args: args,
   149  		Code: result.statusCode,
   150  		Err:  err,
   151  	})
   152  	return result.statusCode, err
   153  }
   154  
   155  type execResult struct {
   156  	failedAtStartup bool
   157  	statusCode      StatusCode
   158  }
   159  
   160  // execCmd spawns a command according to args and passes any signals received
   161  // by the parent process to the spawned process. It returns a bool indicating
   162  // whether the process failed at startup or after starting, the status code of
   163  // the failed process, and an error capturing further information.
   164  func (ex *executor) execCmd(args []string) (execResult, error) {
   165  	result := execResult{true, StatusCode(0)}
   166  	if len(args) == 0 {
   167  		return result, errors.New("args cannot be empty")
   168  	}
   169  
   170  	cmd := exec.Command(args[0], args[1:]...)
   171  
   172  	if ex.stdout == nil {
   173  		cmd.Stdout = os.Stdout
   174  	} else {
   175  		cmd.Stdout = ex.stdout
   176  	}
   177  
   178  	if ex.stderr == nil {
   179  		cmd.Stderr = os.Stderr
   180  	} else {
   181  		cmd.Stderr = ex.stderr
   182  	}
   183  
   184  	if ex.env != nil {
   185  		cmd.Env = ex.env
   186  	}
   187  
   188  	cmd.SysProcAttr = execSyscallAttr
   189  
   190  	if err := cmd.Start(); err != nil {
   191  		return result, err
   192  	}
   193  
   194  	// i.e. indicate we made it past process startup
   195  	result.failedAtStartup = false
   196  
   197  	go ex.passSignals(cmd.Process)
   198  	defer ex.close()
   199  
   200  	// if cmd.Wait returns a nil error it means the process exited with 0
   201  	err := cmd.Wait()
   202  	if err == nil {
   203  		return result, nil
   204  	}
   205  
   206  	// if exited uncleanly, capture status code to report
   207  	status := cmd.ProcessState.Sys().(syscall.WaitStatus)
   208  	result.statusCode = StatusCode(status.ExitStatus())
   209  	return result, err
   210  }
   211  
   212  func (ex *executor) close() {
   213  	close(ex.closeC)
   214  }
   215  
   216  func (ex *executor) closeAndWait() {
   217  	ex.close()
   218  	<-ex.closeDone
   219  }
   220  
   221  // passSignals forwards all signals (except SIGCHLD) received on sigC to the
   222  // process running at proc.
   223  func (ex *executor) passSignals(proc *os.Process) {
   224  	for {
   225  		select {
   226  		case sig := <-ex.sigC:
   227  			if sig.(syscall.Signal) == syscall.SIGCHLD {
   228  				continue
   229  			}
   230  
   231  			ex.handler.SignalReceived(SignalReceivedEvent{
   232  				Signal:   sig,
   233  				ChildPid: proc.Pid,
   234  			})
   235  
   236  			if err := proc.Signal(sig); err != nil {
   237  				ex.handler.SignalFailed(SignalFailedEvent{
   238  					Signal:   sig,
   239  					ChildPid: proc.Pid,
   240  					Err:      err,
   241  				})
   242  				continue
   243  			}
   244  			ex.handler.SignalPassed(SignalPassedEvent{
   245  				Signal:   sig,
   246  				ChildPid: proc.Pid,
   247  			})
   248  
   249  		case <-ex.closeC:
   250  			close(ex.closeDone)
   251  			return
   252  		}
   253  	}
   254  }