Files
crawlab/core/process/daemon.go
2024-06-14 15:42:50 +08:00

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/go-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
}