From 881bd83086d8c3161cd6543b8d76c045a9b9d254 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Thu, 30 Jun 2022 13:31:32 -0600 Subject: [PATCH] Spawn child processes in separate process groups This way signals sent to the parent will not be propagated to the children. The only way to kill children is by cancelling the context passed into the Run method being used. --- pmuxlib/pmuxlib.go | 4 +++- pmuxlib/proc.go | 43 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/pmuxlib/pmuxlib.go b/pmuxlib/pmuxlib.go index 6012b4d..b9d6f7e 100644 --- a/pmuxlib/pmuxlib.go +++ b/pmuxlib/pmuxlib.go @@ -13,7 +13,9 @@ type Config struct { Processes []ProcessConfig `yaml:"processes"` } -// Run runs the given configuration as if this was a real pmux process. +// Run runs the given configuration as if this was a real pmux process. It will +// block until the context is canceled and all child processes have been cleaned +// up. func Run(ctx context.Context, cfg Config) { stdoutLogger := newLogger(os.Stdout, logSepStdout, cfg.TimeFormat) diff --git a/pmuxlib/proc.go b/pmuxlib/proc.go index af8823b..30a77c8 100644 --- a/pmuxlib/proc.go +++ b/pmuxlib/proc.go @@ -10,6 +10,7 @@ import ( "os/exec" "strings" "sync" + "syscall" "time" ) @@ -66,8 +67,26 @@ func (cfg ProcessConfig) withDefaults() ProcessConfig { return cfg } +func sigProcessGroup(sysLogger Logger, proc *os.Process, sig syscall.Signal) { + sysLogger.Printf("sending %v signal", sig) + + // Because we use Setpgid when starting child processes, child processes + // will have the same PGID as their PID. To send a signal to all processes + // in a group, you send the signal to the negation of the PGID, which in + // this case is equivalent to -PID. + // + // POSIX is a fucking joke. + if err := syscall.Kill(-proc.Pid, sig); err != nil { + + panic(fmt.Errorf( + "failed to send %v signal to %d: %w", + sig, -proc.Pid, err, + )) + } +} + // RunProcessOnce runs the process described by the ProcessConfig (though it -// doesn't use all fields from the ProcessConfig). The process is killed if the +// doesn't use all fields from the ProcessConfig). The process is killed iff the // context is canceled. The exit status of the process is returned, or -1 if the // process was never started. // @@ -110,8 +129,16 @@ func RunProcessOnce( cmd := exec.Command(cfg.Cmd, cfg.Args...) cmd.Dir = cfg.Dir - cmd.Env = os.Environ() + cmd.SysProcAttr = &syscall.SysProcAttr{ + // Indicates that the child process should be a part of a separate + // process group than the parent, so that it does not receive signals + // that the parent receives. This is what ensures that context + // cancellation is the only way to interrupt the child processes. + Setpgid: true, + } + + cmd.Env = os.Environ() for k, v := range cfg.Env { cmd.Env = append(cmd.Env, k+"="+v) } @@ -138,18 +165,20 @@ func RunProcessOnce( stopCh := make(chan struct{}) go func(proc *os.Process) { + select { case <-ctx.Done(): + sigProcessGroup(sysLogger, proc, syscall.SIGINT) case <-stopCh: return } select { case <-time.After(cfg.SigKillWait): - sysLogger.Println("forcefully killing process") - _ = proc.Signal(os.Kill) + sigProcessGroup(sysLogger, proc, syscall.SIGKILL) case <-stopCh: } + }(cmd.Process) wg.Wait() @@ -166,12 +195,12 @@ func RunProcessOnce( return exitCode, nil } -// RunProcess is a process (configured by ProcessConfig) until the context is +// RunProcess runs a process (configured by ProcessConfig) until the context is // canceled, at which point the process is killed and RunProcess returns. // // The process will be restarted if it exits of its own accord. There will be a -// brief wait time between each restart, with an exponential backup mechanism so -// that the wait time increases upon repeated restarts. +// brief wait time between each restart, with an exponential backoff mechanism +// so that the wait time increases upon repeated restarts. // // The stdout and stderr of the process will be written to the corresponding // Loggers. Various runtime events will be written to the sysLogger.