package master
|
|
import (
|
"analysis/logo"
|
"context"
|
"os"
|
"os/exec"
|
"os/signal"
|
"syscall"
|
"time"
|
)
|
|
type procInfo struct {
|
cmd *exec.Cmd
|
env string
|
}
|
|
var (
|
procMap = make(map[int]*procInfo)
|
)
|
|
func restartProc(ctxt context.Context, pid int) {
|
info, ok := procMap[pid]
|
if ok {
|
err := info.cmd.Wait()
|
|
if err != nil {
|
logo.Errorln("pid : [", pid, "] quit error: ", err)
|
} else {
|
logo.Infoln("pid : [", pid, "] quit")
|
}
|
delete(procMap, pid)
|
runProc(ctxt, info.cmd.Path, info.cmd.Args[1:], info.env)
|
} else {
|
logo.Errorln(pid, " doesn't exist")
|
}
|
}
|
|
func quitProc(pid int) {
|
info, ok := procMap[pid]
|
if ok {
|
delete(procMap, pid)
|
|
syscall.Kill(pid, syscall.SIGINT)
|
info.cmd.Wait()
|
} else {
|
logo.Errorln(pid, " doesn't exist")
|
}
|
}
|
func runProc(ctxt context.Context, bin string, args []string, env string) (int, error) {
|
cmd := exec.CommandContext(ctxt, bin, args...)
|
rEnv := ""
|
if len(env) != 0 {
|
runtime := "LD_LIBRARY_PATH"
|
rEnv = runtime + "=" + env
|
logo.Infoln("Env String: ", rEnv)
|
|
// remove os environ ld
|
old := os.Getenv(runtime)
|
os.Unsetenv(runtime)
|
cmd.Env = os.Environ()
|
cmd.Env = append(cmd.Env, rEnv)
|
os.Setenv(runtime, old)
|
}
|
|
pid := -1
|
cmd.Stdout = os.Stdout
|
cmd.Stderr = os.Stderr
|
cmd.SysProcAttr = &syscall.SysProcAttr{Pdeathsig: syscall.SIGTERM}
|
|
err := cmd.Start()
|
if err == nil {
|
pid = cmd.Process.Pid
|
procMap[pid] = &procInfo{cmd, env}
|
}
|
return pid, err
|
}
|
|
// Config conf
|
type Config struct {
|
Pid int
|
Options int
|
DisablePid1Check bool
|
}
|
|
// Handle death of child (SIGCHLD) messages. Pushes the signal onto the
|
// notifications channel if there is a waiter.
|
func sigChildHandler(notifications chan os.Signal) {
|
sigs := make(chan os.Signal, 3)
|
signal.Notify(sigs, syscall.SIGCHLD)
|
|
for {
|
sig := <-sigs
|
select {
|
case notifications <- sig: /* published it. */
|
default:
|
/*
|
* Notifications channel full - drop it to the
|
* floor. This ensures we don't fill up the SIGCHLD
|
* queue. The reaper just waits for any child
|
* process (pid=-1), so we ain't loosing it!! ;^)
|
*/
|
}
|
}
|
|
} /* End of function sigChildHandler. */
|
|
// Be a good parent - clean up behind the children.
|
func reapChildren(config Config, pidChan chan<- int) {
|
notifications := make(chan os.Signal, 1)
|
|
go sigChildHandler(notifications)
|
|
pid := config.Pid
|
opts := config.Options
|
|
for {
|
sig := <-notifications
|
logo.Infof(" - Received signal %v\n", sig)
|
for {
|
var wstatus syscall.WaitStatus
|
|
/*
|
* Reap 'em, so that zombies don't accumulate.
|
* Plants vs. Zombies!!
|
*/
|
pid, err := syscall.Wait4(pid, &wstatus, opts, nil)
|
pidChan <- pid
|
for syscall.EINTR == err {
|
pid, err = syscall.Wait4(pid, &wstatus, opts, nil)
|
pidChan <- pid
|
}
|
|
if syscall.ECHILD == err {
|
break
|
}
|
|
logo.Infof(" - Grim reaper cleanup: pid=%d, wstatus=%+v\n",
|
pid, wstatus)
|
|
}
|
}
|
|
} /* End of function reapChildren. */
|
|
/*
|
* ======================================================================
|
* Section: Exported functions
|
* ======================================================================
|
*/
|
|
// Normal entry point for the reaper code. Start reaping children in the
|
// background inside a goroutine.
|
|
// Reap reap
|
func Reap(pidChan chan<- int) {
|
/*
|
* Only reap processes if we are taking over init's duties aka
|
* we are running as pid 1 inside a docker container. The default
|
* is to reap all processes.
|
*/
|
Start(Config{
|
Pid: -1,
|
Options: 0,
|
DisablePid1Check: true,
|
}, pidChan)
|
|
} /* End of [exported] function Reap. */
|
|
// Entry point for invoking the reaper code with a specific configuration.
|
// The config allows you to bypass the pid 1 checks, so handle with care.
|
// The child processes are reaped in the background inside a goroutine.
|
|
// Start start
|
func Start(config Config, pidChan chan<- int) {
|
/*
|
* Start the Reaper with configuration options. This allows you to
|
* reap processes even if the current pid isn't running as pid 1.
|
* So ... use with caution!!
|
*
|
* In most cases, you are better off just using Reap() as that
|
* checks if we are running as Pid 1.
|
*/
|
if !config.DisablePid1Check {
|
mypid := os.Getpid()
|
if 1 != mypid {
|
logo.Errorln(" - Grim reaper disabled, pid not 1\n")
|
return
|
}
|
}
|
|
/*
|
* Ok, so either pid 1 checks are disabled or we are the grandma
|
* of 'em all, either way we get to play the grim reaper.
|
* You will be missed, Terry Pratchett!! RIP
|
*/
|
go reapChildren(config, pidChan)
|
|
} /* End of [exported] function Start. */
|
|
func waitForRestart(ctxt context.Context, pidChan <-chan int) {
|
|
for {
|
select {
|
case <-ctxt.Done():
|
return
|
case pid := <-pidChan:
|
restartProc(ctxt, pid)
|
default:
|
time.Sleep(3 * time.Second)
|
}
|
}
|
}
|