godaemon/godaemon.go

201 lines
3.9 KiB
Go
Raw Normal View History

2023-12-10 17:56:48 +08:00
package godaemon
import (
"flag"
"fmt"
"os"
"os/exec"
"os/signal"
"path/filepath"
"strconv"
"strings"
"syscall"
"git.pyer.club/kingecg/gologger"
)
const (
daemon_env_key = "_go_daemon"
daemon_process = "g_daemon"
daemon_task = "g_dtask"
daemon_taskargs = "g_args"
)
type GoDaemon struct {
2024-10-02 18:38:36 +08:00
pidFile string
taskPidFile string
flag *string
sigChan chan os.Signal
state string
2024-10-01 18:51:49 +08:00
*gologger.Logger
2023-12-10 18:36:16 +08:00
Running *exec.Cmd
StartFn func(*GoDaemon)
StopFn func(*GoDaemon)
2023-12-10 17:56:48 +08:00
}
func (g *GoDaemon) GetPid() int {
pids, ferr := os.ReadFile(g.pidFile)
pid, err := strconv.Atoi(string(pids))
if err != nil || ferr != nil {
return 0
}
return pid
}
2024-10-02 18:38:36 +08:00
func (g *GoDaemon) GetTaskPid() int {
pids, ferr := os.ReadFile(g.taskPidFile)
pid, err := strconv.Atoi(string(pids))
if err != nil || ferr != nil {
return 0
}
return pid
}
2023-12-10 17:56:48 +08:00
func (g *GoDaemon) Start() {
if g.flag == nil {
g.flag = flag.String("s", "", "send signal to daemon. support: reload and quit")
}
if IsMaster() {
flag.Parse()
if *g.flag == "" {
g.startDaemon()
return
}
var sig syscall.Signal
if *g.flag == "reload" {
sig = syscall.SIGHUP
} else if *g.flag == "quit" {
sig = syscall.SIGTERM
} else {
fmt.Println("Not supported signal")
return
}
p := g.getDaemonProcess()
if p == nil {
fmt.Println("Daemon process not found")
return
}
2023-12-10 18:36:16 +08:00
g.Debug("Send signal:", p.Pid, sig)
2023-12-10 17:56:48 +08:00
p.Signal(sig)
} else if IsDaemon() {
pid := os.Getpid()
os.WriteFile(g.pidFile, []byte(strconv.Itoa(pid)), 0644)
g.sigChan = make(chan os.Signal)
2023-12-10 18:36:16 +08:00
2023-12-10 17:56:48 +08:00
signal.Notify(g.sigChan, syscall.SIGTERM, syscall.SIGHUP)
go g.serveSignal()
for {
2023-12-10 18:36:16 +08:00
g.Debug("Starting new task")
2023-12-10 17:56:48 +08:00
g.Running = g.startTask()
g.state = "running"
g.Running.Process.Wait()
if g.state == "stopped" {
2023-12-10 18:36:16 +08:00
g.Debug("daemon is stopped, exit now")
2023-12-10 17:56:48 +08:00
break
}
}
} else {
2024-10-02 18:38:36 +08:00
waiter := make(chan os.Signal, 1)
2023-12-10 17:56:48 +08:00
g.StartFn(g)
2024-10-02 18:38:36 +08:00
g.Info("daemon task is started")
<-waiter
g.Info("daemon task will be stopped")
g.StopFn(g)
2023-12-10 17:56:48 +08:00
}
}
func (g *GoDaemon) serveSignal() {
sig := <-g.sigChan
if sig == syscall.SIGTERM {
g.state = "stopped"
} else {
g.state = "restart"
}
2023-12-10 18:36:16 +08:00
2024-10-02 18:38:36 +08:00
g.Running.Process.Signal(syscall.SIGTERM)
2023-12-10 17:56:48 +08:00
}
func (g *GoDaemon) getDaemonProcess() *os.Process {
pid := g.GetPid()
if pid == 0 {
return nil
}
p, err := os.FindProcess(pid)
if err != nil {
2023-12-10 18:36:16 +08:00
g.Debug(err)
2023-12-10 17:56:48 +08:00
}
serr := p.Signal(syscall.Signal(0))
if serr != nil {
return nil
}
return p
}
func (g *GoDaemon) startDaemon() {
dp := g.getDaemonProcess()
if dp != nil {
fmt.Println("daemon is running with pid:", dp.Pid)
return
}
execName, _ := os.Executable()
cmd := exec.Command(execName)
cmd.Env = append(cmd.Env, daemon_env_key+"="+daemon_process)
pargs := os.Args[1:]
cmd.Env = append(cmd.Env, daemon_taskargs+"="+strings.Join(pargs, ";"))
cmd.Start()
}
func (g *GoDaemon) startTask() *exec.Cmd {
extraArgs, _ := os.LookupEnv(daemon_taskargs)
var cmd *exec.Cmd
execName, _ := os.Executable()
if extraArgs != "" {
eargs := strings.Split(extraArgs, ";")
cmd = exec.Command(execName, eargs...)
} else {
cmd = exec.Command(execName)
}
cmd.Env = append(cmd.Env, daemon_env_key+"="+daemon_task)
cmd.Start()
return cmd
}
func IsMaster() bool {
goDaemonEnv, _ := os.LookupEnv(daemon_env_key)
return goDaemonEnv == ""
}
func IsDaemon() bool {
goDaemonEnv, _ := os.LookupEnv(daemon_env_key)
return goDaemonEnv == daemon_process
}
func IsDaemonTask() bool {
goDaemonEnv, _ := os.LookupEnv(daemon_env_key)
return goDaemonEnv == daemon_task
}
func NewGoDaemon(start, stop func(*GoDaemon)) *GoDaemon {
2023-12-10 18:36:16 +08:00
godaemon := &GoDaemon{
Logger: gologger.GetLogger("daemon"),
}
2023-12-10 17:56:48 +08:00
execName, _ := os.Executable()
if filepath.Ext(execName) != "" {
execName = strings.TrimSuffix(execName, filepath.Ext(execName))
}
godaemon.pidFile = execName + ".pid"
2024-10-02 18:38:36 +08:00
godaemon.taskPidFile = execName + ".task.pid"
2023-12-10 17:56:48 +08:00
godaemon.StartFn = start
godaemon.StopFn = stop
return godaemon
}