mirror of
https://github.com/crawlab-team/crawlab.git
synced 2026-01-22 17:31:03 +01:00
171 lines
3.1 KiB
Go
171 lines
3.1 KiB
Go
package process
|
|
|
|
import (
|
|
"github.com/apex/log"
|
|
"github.com/crawlab-team/crawlab/core/errors"
|
|
"github.com/crawlab-team/crawlab/core/interfaces"
|
|
"github.com/crawlab-team/crawlab/core/sys_exec"
|
|
"github.com/crawlab-team/crawlab/trace"
|
|
"math/rand"
|
|
"os/exec"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
SignalCreate = iota
|
|
SignalStart
|
|
SignalStopped
|
|
SignalError
|
|
SignalExited
|
|
SignalReachedMaxErrors
|
|
)
|
|
|
|
type Daemon struct {
|
|
// settings
|
|
maxErrors int
|
|
exitTimeout time.Duration
|
|
|
|
// internals
|
|
errors int
|
|
errMsg string
|
|
exitCode int
|
|
newCmdFn func() *exec.Cmd
|
|
cmd *exec.Cmd
|
|
stopped bool
|
|
ch chan int
|
|
}
|
|
|
|
func (d *Daemon) Start() (err error) {
|
|
go d.handleSignal()
|
|
|
|
for {
|
|
// command
|
|
d.cmd = d.newCmdFn()
|
|
d.ch <- SignalCreate
|
|
|
|
// attempt to run
|
|
_ = d.cmd.Start()
|
|
d.ch <- SignalStart
|
|
|
|
if err := d.cmd.Wait(); err != nil {
|
|
// stopped
|
|
d.ch <- SignalStopped
|
|
if d.stopped {
|
|
log.Infof("daemon stopped")
|
|
return nil
|
|
}
|
|
|
|
// error
|
|
d.ch <- SignalError
|
|
d.errMsg = err.Error()
|
|
trace.PrintError(err)
|
|
}
|
|
|
|
// exited
|
|
d.ch <- SignalExited
|
|
|
|
// exit code
|
|
d.exitCode = d.cmd.ProcessState.ExitCode()
|
|
|
|
// check exit code
|
|
if d.exitCode == 0 {
|
|
log.Infof("process exited with code 0")
|
|
return
|
|
}
|
|
|
|
// error message
|
|
d.errMsg = errors.ErrorProcessDaemonProcessExited.Error()
|
|
|
|
// increment errors
|
|
d.errors++
|
|
|
|
// validate if error count exceeds max errors
|
|
if d.errors >= d.maxErrors {
|
|
log.Infof("reached max errors: %d", d.maxErrors)
|
|
d.ch <- SignalReachedMaxErrors
|
|
return errors.ErrorProcessReachedMaxErrors
|
|
}
|
|
|
|
// re-attempt
|
|
waitSec := rand.Intn(5)
|
|
log.Infof("re-attempt to start process in %d seconds...", waitSec)
|
|
time.Sleep(time.Duration(waitSec) * time.Second)
|
|
}
|
|
}
|
|
|
|
func (d *Daemon) Stop() {
|
|
d.stopped = true
|
|
opts := &sys_exec.KillProcessOptions{
|
|
Timeout: d.exitTimeout,
|
|
Force: false,
|
|
}
|
|
_ = sys_exec.KillProcess(d.cmd, opts)
|
|
}
|
|
|
|
func (d *Daemon) GetMaxErrors() (maxErrors int) {
|
|
return d.maxErrors
|
|
}
|
|
|
|
func (d *Daemon) SetMaxErrors(maxErrors int) {
|
|
d.maxErrors = maxErrors
|
|
}
|
|
|
|
func (d *Daemon) GetExitTimeout() (timeout time.Duration) {
|
|
return d.exitTimeout
|
|
}
|
|
|
|
func (d *Daemon) SetExitTimeout(timeout time.Duration) {
|
|
d.exitTimeout = timeout
|
|
}
|
|
|
|
func (d *Daemon) GetCmd() (cmd *exec.Cmd) {
|
|
return d.cmd
|
|
}
|
|
|
|
func (d *Daemon) GetCh() (ch chan int) {
|
|
return d.ch
|
|
}
|
|
|
|
func (d *Daemon) handleSignal() {
|
|
for {
|
|
select {
|
|
case signal := <-d.ch:
|
|
switch signal {
|
|
case SignalCreate:
|
|
log.Infof("process created")
|
|
case SignalStart:
|
|
log.Infof("process started")
|
|
case SignalStopped:
|
|
log.Infof("process stopped")
|
|
case SignalError:
|
|
trace.PrintError(errors.NewProcessError(d.errMsg))
|
|
case SignalExited:
|
|
log.Infof("process exited")
|
|
case SignalReachedMaxErrors:
|
|
log.Infof("reached max errors")
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func NewProcessDaemon(newCmdFn func() *exec.Cmd, opts ...DaemonOption) (d interfaces.ProcessDaemon) {
|
|
// daemon
|
|
d = &Daemon{
|
|
maxErrors: 5,
|
|
exitTimeout: 15 * time.Second,
|
|
errors: 0,
|
|
errMsg: "",
|
|
newCmdFn: newCmdFn,
|
|
stopped: false,
|
|
ch: make(chan int),
|
|
}
|
|
|
|
// apply options
|
|
for _, opt := range opts {
|
|
opt(d)
|
|
}
|
|
|
|
return d
|
|
}
|