feat: added modules

This commit is contained in:
Marvin Zhang
2024-06-14 15:42:50 +08:00
parent f1833fed21
commit 0b67fd9ece
626 changed files with 60104 additions and 0 deletions

170
core/process/daemon.go Normal file
View 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
}

View 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
View 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
View 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) {
}
}