blob: c832caa7b5e26b1c559b041793c71f9c1e99f619 [file] [log] [blame]
//go:build linux
package claudetool
import (
"log/slog"
"os"
"syscall"
"time"
"golang.org/x/sys/unix"
)
// reapZombies attempts to reap zombie child processes from the specified
// process group that may have been left behind after a process group cleanup.
// This is important when running as PID 1 (init process) since no other process
// will reap zombies.
//
// This function reaps zombies until the process group is empty or no more
// zombies are available.
func reapZombies(pgid int) {
if os.Getpid() != 1 {
return // not running as init (e.g. -unsafe), no need to reap
}
// Quick exit for the common case.
if !processGroupHasProcesses(pgid) {
return // no processes in the group, nothing to reap
}
// Reap in the background.
go func() {
maxAttempts := 1000 // shouldn't ever hit this, so be generous, this isn't particularly expensive
for range maxAttempts {
if !processGroupHasProcesses(pgid) {
return
}
var wstatus unix.WaitStatus
pid, err := unix.Wait4(-pgid, &wstatus, unix.WNOHANG, nil)
switch err {
case syscall.EINTR:
// interrupted, retry
continue
case syscall.ECHILD:
// no children, therefore no zombies
return
case nil:
// fall through to handle pid
default:
slog.Debug("unexpected error in reapZombies", "error", err, "pgid", pgid)
return
}
if pid == 0 {
// No zombies available right now, wait and check again
// There's no great rush, so give it some time.
time.Sleep(100 * time.Millisecond)
continue
}
slog.Debug("reaped zombie process", "pid", pid, "pgid", pgid, "status", wstatus)
}
}()
}