Simple process manager helper library, features:
- start processes
.AddCommand(Cmd)
and.Start(cmdId)
or.StartAll()
, with environment variablesCmd.Env=[]string{}
andCmd.InheritEnv=true
- stop them;
.Kill(cmdId)
or.Cleanup()
to kill all process - restart them when they crash; using
Cmd.RestartDelayMs=1000
(=1s, default is 0) andCmd.MaxRestart=5
(restart 5x if process ended/crashed) - relay termination signals;
.Signal(cmdId, os.Kill)
- read their stdout and stderr;
Cmd.OnStdout
,Cmd.OnStdErr
callback - ability to stop processes when main processes are SIGKILL'ed:
.Cleanup()
called automatically when main process killed; - configurable backoff strategy for restarts; you can use
Cmd.OnRestart
callback to return random delay or implement your own exponential backoff, setting this callback will renderCmd.RestartDelayMs
unusable Cmd.OnExit
callback when no more restart reached, you can call.Start(cmdId)
manually again after thisCmd.OnProcessCompleted
callback each time program completed once (before restarting if MaxRestart not yet reached)Cmd.StartDelayMs=1000
(=1s, default is 0) for delaying start, in millisecondsCmd.UseChannelApi=true
, if enabled, you can receive from channels:Cmd.StderrChannel
,Cmd.OnStdoutChannel
,Cmd.ProcessCompletedChannel
,Cmd.ExitChannel
Cmd.LastExecutionError
property to get last process execution error, check this answer to get the exit codeCmd.OnStateChanged
callback andCmd.StateChangedChannel
channel to track process state- should work on Linux, and probably MacOS and Windows (untested).
- see example/ for other usage example/demo;
versioning using this format 1.(M+(YEAR-2021)*12)DD
.HMM
,
so for example v1.213.1549 means it was released at 2021-02-13 15:49
daemon := goproc.New()
cmdId := daemon.AddCommand(&goproc.Cmd{
Program: `sleep`, // program to run
WorkDir: `/tmp`, // directory to run the program (optional)
Parameters: []string{`2`}, // command line arguments
MaxRestart: goproc.RestartForever, // default: NoRestart=0
OnStderr: func(cmd *goproc.Cmd, line string) error { // optional
fmt.Println(`OnStderr: `+line)
return nil
},
OnStdout: func(cmd *goproc.Cmd, line string) error { // optional
fmt.Println(`OnStdout: `+line)
return nil
},
})
daemon.Start(cmdId) // use "go" keyword if you need non-blocking version
// ^ by default will only run next command if previous command exited
// if you want to run them in parralel, use daemon.StartParallel().Wait()
daemon.CommandString(cmdId) // returns "sleep 2"
Alternatively there's shortcut with Run1
returns string stdout
and stderr
, error
, and exitCode
. Also there's RunLines
returns []string
instead of string
.
Q: Why not just channel? why callback?
A: Because channel requires a consumer, or it would stuck/block if channel is full, while callback doesn't (this is why I add flag to activate the channel API). To the Percona reviewer that rejected me because I didn't use channel at the first time "the whole thing is written in JavaScript translated to Go, not in Go. Technical task does not adhere to Go best practices which is what would expect from the candidate. One example was that the code is written in JS-like style (e.g. callback). Go code does not use callback like node.js for example", well, jokes on you even net/http.HandleFunc
uses callback XD
Q: How to ignore error being printed?
A: assign Goproc.HasErrFunc
with goproc.DiscardHasErr
, other option are: L.IsError
(default), goproc.LogHasErr
(uses log), goproc.PrintHasErr
(uses fmt), or you can always create your own.
- implement
.Pause
and.Resume
API - comments and documentation in code;
- continuous integration configuration;
- integration tests;
- unit tests.