Files
supercronic/reaper.go
Ashley Mathew cfd6038f87 fix(reaper): forkExec error no such file or directory & prometheus exit error
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.
2025-09-24 14:43:45 -04:00

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. */