mirror of
https://github.com/aptible/supercronic.git
synced 2025-09-26 20:31:17 +08:00

This is the fix described in https://github.com/aptible/supercronic/pull/178 but with minimal changes applied, since we want to limit the scope of changes of an individual PR.
129 lines
2.9 KiB
Go
129 lines
2.9 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
func forkExec() {
|
|
|
|
// run supercronic in other process
|
|
pwd, err := os.Getwd()
|
|
if err != nil {
|
|
logrus.Fatalf("Failed to get current working directory: %s", err.Error())
|
|
return
|
|
}
|
|
|
|
exe, err := os.Executable()
|
|
if err != nil {
|
|
logrus.Fatalf("Failed to get executable %s", err.Error())
|
|
}
|
|
|
|
pattrs := &syscall.ProcAttr{
|
|
Dir: pwd,
|
|
Env: os.Environ(),
|
|
Files: []uintptr{
|
|
uintptr(syscall.Stdin),
|
|
uintptr(syscall.Stdout),
|
|
uintptr(syscall.Stderr),
|
|
},
|
|
}
|
|
args := make([]string, 0, len(os.Args)+1)
|
|
// disable reaping for supercronic, avoid no sense warning
|
|
args = append(args, exe, "-no-reap")
|
|
args = append(args, os.Args[1:]...)
|
|
|
|
pid, err := syscall.ForkExec(args[0], args, pattrs)
|
|
if err != nil {
|
|
logrus.Fatalf("Failed to fork exec: %s", err.Error())
|
|
return
|
|
}
|
|
|
|
// forward signal to supercronic
|
|
signalToFork(pid)
|
|
// got supercronic exit status
|
|
wstatus := reapChildren(pid)
|
|
os.Exit(wstatus.ExitStatus())
|
|
}
|
|
|
|
func signalToFork(pid int) {
|
|
p, err := os.FindProcess(pid)
|
|
if err != nil {
|
|
logrus.Fatalf("Failed findProcess supercronic pid:%d,%s", pid, err.Error())
|
|
}
|
|
termChan := make(chan os.Signal, 1)
|
|
signal.Notify(termChan, signalList...)
|
|
go func() {
|
|
for {
|
|
s := <-termChan
|
|
if err := p.Signal(s); err != nil {
|
|
logrus.Errorf("Failed to send signal to supercronic: %s", err.Error())
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
// copy from https://github.com/ramr/go-reaper
|
|
// modify for wait exit status of supercronic
|
|
// without modify, supercronic exit status may not be obtained
|
|
|
|
// Be a good parent - clean up behind the children.
|
|
func reapChildren(superCrondPid int) syscall.WaitStatus {
|
|
var notifications = make(chan os.Signal, 1)
|
|
|
|
go sigChildHandler(notifications)
|
|
|
|
// all child
|
|
const rpid = -1
|
|
var wstatus syscall.WaitStatus
|
|
|
|
for {
|
|
var sig = <-notifications
|
|
logrus.Debugf("reaper received signal %v\n", sig)
|
|
for {
|
|
pid, err := syscall.Wait4(rpid, &wstatus, 0, nil)
|
|
for syscall.EINTR == err {
|
|
pid, err = syscall.Wait4(pid, &wstatus, 0, nil)
|
|
}
|
|
|
|
if syscall.ECHILD == err {
|
|
break
|
|
}
|
|
|
|
if superCrondPid == pid {
|
|
logrus.Debugf("supercronic exit, pid=%d, wstatus=%+v, err=%+v\n", pid, wstatus, err)
|
|
return wstatus
|
|
}
|
|
// note: change output need change test
|
|
logrus.Warnf("reaper cleanup: pid=%d, wstatus=%+v\n",
|
|
pid, wstatus)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// Handle death of child (SIGCHLD) messages. Pushes the signal onto the
|
|
// notifications channel if there is a waiter.
|
|
func sigChildHandler(notifications chan os.Signal) {
|
|
var sigs = make(chan os.Signal, 3)
|
|
signal.Notify(sigs, syscall.SIGCHLD)
|
|
|
|
for {
|
|
var 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. */
|