first commit
This commit is contained in:
210
main.go
Normal file
210
main.go
Normal file
@ -0,0 +1,210 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
CMD string `json:"cmd"`
|
||||
Args []string `json:"args"`
|
||||
PWD string `json:"pwd"`
|
||||
|
||||
cmd *exec.Cmd
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Services []*Service `json:"services"`
|
||||
}
|
||||
|
||||
func printErr(err error) {
|
||||
fmt.Fprintf(os.Stderr, "[qini] Err: %s\n", err.Error())
|
||||
}
|
||||
|
||||
func printInfo(info string) {
|
||||
fmt.Fprintf(os.Stderr, "[qini] Inf: %s\n", info)
|
||||
}
|
||||
|
||||
var verbose bool
|
||||
|
||||
func debug(s string, a ...any) {
|
||||
if verbose {
|
||||
fmt.Fprintf(os.Stderr, "[qini] debug: %s\n", fmt.Sprintf(s, a...))
|
||||
}
|
||||
}
|
||||
|
||||
func stopAllServices(services []*Service) {
|
||||
for _, s := range services {
|
||||
if s.cmd.Process == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
debug("send SIGINT to [%s]", s.CMD)
|
||||
s.cmd.Process.Signal(syscall.SIGINT)
|
||||
|
||||
go func(s *Service) {
|
||||
time.Sleep(15 * time.Second)
|
||||
if s.cmd.Process != nil && s.cmd.ProcessState == nil {
|
||||
debug("send SIGKILL to [%s]", s.CMD)
|
||||
s.cmd.Process.Kill()
|
||||
}
|
||||
}(s)
|
||||
}
|
||||
}
|
||||
|
||||
func runServices(services []*Service) int {
|
||||
if len(services) == 0 {
|
||||
printInfo("no services in config")
|
||||
return 1
|
||||
}
|
||||
|
||||
var (
|
||||
r *io.PipeReader
|
||||
w *io.PipeWriter
|
||||
|
||||
lock sync.Mutex
|
||||
once sync.Once
|
||||
wg sync.WaitGroup
|
||||
|
||||
exitCode int
|
||||
)
|
||||
|
||||
lock.Lock()
|
||||
|
||||
for i, s := range services {
|
||||
s.cmd = exec.Command(s.CMD, s.Args...)
|
||||
|
||||
s.cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
Setsid: false,
|
||||
}
|
||||
|
||||
if s.PWD != "" {
|
||||
s.cmd.Dir = s.PWD
|
||||
}
|
||||
|
||||
if r != nil {
|
||||
s.cmd.Stdin = r
|
||||
}
|
||||
|
||||
if i+1 < len(services) {
|
||||
r, w = io.Pipe()
|
||||
s.cmd.Stdout = w
|
||||
} else {
|
||||
s.cmd.Stdout = os.Stdout
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func(w *io.PipeWriter) {
|
||||
defer wg.Done()
|
||||
|
||||
defer func() {
|
||||
if w != nil {
|
||||
w.Close()
|
||||
}
|
||||
|
||||
once.Do(func() {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
debug("stop all services")
|
||||
|
||||
stopAllServices(services)
|
||||
exitCode = s.cmd.ProcessState.ExitCode()
|
||||
})
|
||||
}()
|
||||
|
||||
debug("run service [%s]", s.CMD)
|
||||
err := s.cmd.Run()
|
||||
if err != nil {
|
||||
printErr(err)
|
||||
}
|
||||
debug("service [%s] done, err: [%v]", s.CMD, err)
|
||||
}(w)
|
||||
}
|
||||
|
||||
lock.Unlock()
|
||||
|
||||
debug("wait servises")
|
||||
wg.Wait()
|
||||
debug("all servises stoped")
|
||||
|
||||
return exitCode
|
||||
}
|
||||
|
||||
func loadConfig(cfg *Config, configFile string) error {
|
||||
f, err := os.Open(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
return json.NewDecoder(f).Decode(cfg)
|
||||
}
|
||||
|
||||
func forwardSignals(services []*Service) {
|
||||
signals := []os.Signal{syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT}
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, signals...)
|
||||
|
||||
for sig := range sigCh {
|
||||
for _, s := range services {
|
||||
if s.cmd.Process == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
syscall.Kill(s.cmd.Process.Pid, (sig).(syscall.Signal))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func forwardSignals2(services []*Service) {
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, syscall.SIGCHLD)
|
||||
|
||||
for _ = range sigCh {
|
||||
printInfo("chld")
|
||||
for _, c := range services {
|
||||
if c.cmd.ProcessState != nil {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
verbose = true
|
||||
|
||||
err := unix.Prctl(unix.PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0)
|
||||
if err != nil {
|
||||
printErr(err)
|
||||
}
|
||||
|
||||
var configFile string
|
||||
flag.StringVar(&configFile, "c", "./config.json", "JSON config file")
|
||||
flag.Parse()
|
||||
|
||||
var cfg Config
|
||||
|
||||
err = loadConfig(&cfg, configFile)
|
||||
if err != nil {
|
||||
printErr(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
go forwardSignals(cfg.Services)
|
||||
go forwardSignals2(cfg.Services)
|
||||
|
||||
exitCode := runServices(cfg.Services)
|
||||
os.Exit(exitCode)
|
||||
}
|
Reference in New Issue
Block a user