diff --git a/hivemind.go b/hivemind.go index e2217c0..d569fc3 100644 --- a/hivemind.go +++ b/hivemind.go @@ -21,6 +21,8 @@ type hivemindConfig struct { Timeout int NoPrefix bool PrintTimestamps bool + ExitWithHighest bool + AsJobRunner bool } type hivemind struct { @@ -31,6 +33,7 @@ type hivemind struct { done chan bool interrupted chan os.Signal timeout time.Duration + jobRunner bool } func newHivemind(conf hivemindConfig) (h *hivemind) { @@ -43,6 +46,7 @@ func newHivemind(conf hivemindConfig) (h *hivemind) { } h.output = &multiOutput{printProcName: !conf.NoPrefix, printTimestamp: conf.PrintTimestamps} + h.jobRunner = conf.AsJobRunner entries := parseProcfile(conf.Procfile, conf.PortBase, conf.PortStep) h.procs = make([]*process, 0) @@ -62,17 +66,32 @@ func (h *hivemind) runProcess(proc *process) { h.procWg.Add(1) go func() { + procSucceed := false + defer h.procWg.Done() - defer func() { h.done <- true }() + defer func() { h.done <- procSucceed }() - proc.Run() + procSucceed = proc.Run() }() } -func (h *hivemind) waitForDoneOrInterrupt() { +func (h *hivemind) waitForDoneOrInterrupt() bool { select { - case <-h.done: + case done := <-h.done: + return done case <-h.interrupted: + return false + } +} + +func (h *hivemind) waitForJobsToCompleteOrInterrupt() { + jobsCount := len(h.procs) + + for jobsCompleted := 0; jobsCompleted < jobsCount; jobsCompleted++ { + succeeded := h.waitForDoneOrInterrupt() + if !succeeded { + return + } } } @@ -84,7 +103,11 @@ func (h *hivemind) waitForTimeoutOrInterrupt() { } func (h *hivemind) waitForExit() { - h.waitForDoneOrInterrupt() + if h.jobRunner { + h.waitForJobsToCompleteOrInterrupt() + } else { + h.waitForDoneOrInterrupt() + } for _, proc := range h.procs { go proc.Interrupt() @@ -97,7 +120,7 @@ func (h *hivemind) waitForExit() { } } -func (h *hivemind) Run() { +func (h *hivemind) Run() int { fmt.Printf("\033]0;%s | hivemind\007", h.title) h.done = make(chan bool, len(h.procs)) @@ -112,4 +135,15 @@ func (h *hivemind) Run() { go h.waitForExit() h.procWg.Wait() + + exitCode := 0 + + for _, proc := range h.procs { + code := proc.ProcessState.ExitCode() + if code > exitCode { + exitCode = code + } + } + + return exitCode } diff --git a/main.go b/main.go index 7c2bf58..b97207e 100644 --- a/main.go +++ b/main.go @@ -30,14 +30,16 @@ func main() { app.HideHelp = true app.Flags = []cli.Flag{ - cli.StringFlag{Name: "title, w", EnvVar: "HIVEMIND_TITLE", Usage: "Specify a title of the application", Destination: &conf.Title}, - cli.StringFlag{Name: "processes, l", EnvVar: "HIVEMIND_PROCESSES", Usage: "Specify process names to launch. Divide names with comma", Destination: &conf.ProcNames}, + cli.StringFlag{Name: "title, w", EnvVar: "HIVEMIND_TITLE", Usage: "specify a title of the application", Destination: &conf.Title}, + cli.StringFlag{Name: "processes, l", EnvVar: "HIVEMIND_PROCESSES", Usage: "specify process names to launch. Divide names with comma", Destination: &conf.ProcNames}, cli.IntFlag{Name: "port, p", EnvVar: "HIVEMIND_PORT,PORT", Usage: "specify a port to use as the base", Value: 5000, Destination: &conf.PortBase}, cli.IntFlag{Name: "port-step, P", EnvVar: "HIVEMIND_PORT_STEP", Usage: "specify a step to increase port number", Value: 100, Destination: &conf.PortStep}, cli.StringFlag{Name: "root, d", EnvVar: "HIVEMIND_ROOT", Usage: "specify a working directory of application. Default: directory containing the Procfile", Destination: &conf.Root}, cli.IntFlag{Name: "timeout, t", EnvVar: "HIVEMIND_TIMEOUT", Usage: "specify the amount of time (in seconds) processes have to shut down gracefully before being brutally killed", Value: 5, Destination: &conf.Timeout}, cli.BoolFlag{Name: "no-prefix", EnvVar: "HIVEMIND_NO_PREFIX", Usage: "process names will not be printed if the flag is specified", Destination: &conf.NoPrefix}, cli.BoolFlag{Name: "print-timestamps, T", EnvVar: "HIVEMIND_PRINT_TIMESTAMPS", Usage: "timestamps will be printed if the flag is specified", Destination: &conf.PrintTimestamps}, + cli.BoolFlag{Name: "exit-with-highest-exit-code, e", EnvVar: "HIVEMIND_EXIT_WITH_HIGHEST_EXIT_CODE", Usage: "exit hivemind with highest exit code from all processes", Destination: &conf.ExitWithHighest}, + cli.BoolFlag{Name: "as-job-runner", EnvVar: "HIVEMIND_AS_JOB_RUNNER", Usage: "be a job runner instead: Will wait for all to finish with exit code 0, or fail.", Destination: &conf.AsJobRunner}, } app.Action = func(c *cli.Context) error { @@ -65,7 +67,10 @@ func main() { conf.Root, err = filepath.Abs(conf.Root) fatalOnErr(err) - newHivemind(conf).Run() + exitCode := newHivemind(conf).Run() + if exitCode > 0 && conf.ExitWithHighest { + return cli.NewExitError("At least one process failed", exitCode) + } return nil } diff --git a/process.go b/process.go index 45a9571..7ead03a 100644 --- a/process.go +++ b/process.go @@ -56,7 +56,7 @@ func (p *process) Running() bool { return p.Process != nil && p.ProcessState == nil } -func (p *process) Run() { +func (p *process) Run() bool { p.output.PipeOutput(p) defer p.output.ClosePipe(p) @@ -66,8 +66,10 @@ func (p *process) Run() { if err := p.Cmd.Run(); err != nil { p.writeErr(err) + return false } else { p.writeLine([]byte("\033[1mProcess exited\033[0m")) + return true } } diff --git a/test-as-job-runner.sh b/test-as-job-runner.sh new file mode 100755 index 0000000..5e42518 --- /dev/null +++ b/test-as-job-runner.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +echo +echo "As process manager" +dist/hivemind --exit-with-highest-exit-code - <