mirror of
https://github.com/crawlab-team/crawlab.git
synced 2026-01-21 17:21:09 +01:00
feat: added modules
This commit is contained in:
170
core/process/daemon.go
Normal file
170
core/process/daemon.go
Normal file
@@ -0,0 +1,170 @@
|
||||
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
|
||||
}
|
||||
21
core/process/daemon_test.go
Normal file
21
core/process/daemon_test.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"os/exec"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDaemon(t *testing.T) {
|
||||
d := NewProcessDaemon(func() *exec.Cmd {
|
||||
return exec.Command("echo", "hello")
|
||||
})
|
||||
err := d.Start()
|
||||
require.Nil(t, err)
|
||||
|
||||
d = NewProcessDaemon(func() *exec.Cmd {
|
||||
return exec.Command("return", "1")
|
||||
})
|
||||
err = d.Start()
|
||||
require.NotNil(t, err)
|
||||
}
|
||||
60
core/process/manage.go
Normal file
60
core/process/manage.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"github.com/crawlab-team/go-trace"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var pidRegexp, _ = regexp.Compile("(?:^|\\s+)\\d+(?:$|\\s+)")
|
||||
|
||||
func ProcessIdExists(id int) (ok bool) {
|
||||
lines, err := ListProcess(string(rune(id)))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, line := range lines {
|
||||
matched := pidRegexp.MatchString(line)
|
||||
if matched {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func ListProcess(text string) (lines []string, err error) {
|
||||
if runtime.GOOS == "windows" {
|
||||
return listProcessWindow(text)
|
||||
} else {
|
||||
return listProcessLinuxMac(text)
|
||||
}
|
||||
}
|
||||
|
||||
func listProcessWindow(text string) (lines []string, err error) {
|
||||
cmd := exec.Command("tasklist", "/fi", text)
|
||||
out, err := cmd.CombinedOutput()
|
||||
_, ok := err.(*exec.ExitError)
|
||||
if !ok {
|
||||
return nil, trace.TraceError(err)
|
||||
}
|
||||
lines = strings.Split(string(out), "\n")
|
||||
return lines, nil
|
||||
}
|
||||
|
||||
func listProcessLinuxMac(text string) (lines []string, err error) {
|
||||
cmd := exec.Command("ps", "aux")
|
||||
out, err := cmd.CombinedOutput()
|
||||
_, ok := err.(*exec.ExitError)
|
||||
if !ok {
|
||||
return nil, trace.TraceError(err)
|
||||
}
|
||||
_lines := strings.Split(string(out), "\n")
|
||||
for _, l := range _lines {
|
||||
if strings.Contains(l, text) {
|
||||
lines = append(lines, l)
|
||||
}
|
||||
}
|
||||
return lines, nil
|
||||
}
|
||||
20
core/process/options.go
Normal file
20
core/process/options.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"github.com/crawlab-team/crawlab/core/interfaces"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DaemonOption func(d interfaces.ProcessDaemon)
|
||||
|
||||
func WithDaemonMaxErrors(maxErrors int) DaemonOption {
|
||||
return func(d interfaces.ProcessDaemon) {
|
||||
d.SetMaxErrors(maxErrors)
|
||||
}
|
||||
}
|
||||
|
||||
func WithExitTimeout(timeout time.Duration) DaemonOption {
|
||||
return func(d interfaces.ProcessDaemon) {
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user