fix(host): tie worker lifetime to the daemon (no orphan leak on crash)

Pairs the process-group isolation (setsid) with explicit parent-death cleanup so
a worker never lingers if the daemon dies WITHOUT cleaning it up (a crash).
setsid detaches the worker from the launcher's controlling terminal, which also
removes the incidental SIGHUP that used to reap orphans — so we replace it with
something reliable: PR_SET_PDEATHSIG(SIGKILL) on Linux (kernel-level, immediate),
plus a portable getppid() watchdog (covers macOS and backs up PDEATHSIG) that
exits if our parent changes. Compares against the daemon's actual pid (not pid
1) so a daemon that is itself PID 1 (a container) is handled correctly. Graceful
shutdown is unchanged (daemon still kills workers per-PID).

Verified: workers isolate into their own group; after kill -9 of the daemon,
every worker exits on its own (no leak).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dario Gabriel Lipicar
2026-06-17 16:09:39 -03:00
co-authored by Claude Opus 4.8
parent fd00bcbccb
commit 272c6b5bc7
@@ -9,9 +9,13 @@
#include <csignal>
#include <cstddef>
#include <cstdint>
#include <thread>
#include <execinfo.h>
#include <unistd.h>
#ifdef __linux__
#include <sys/prctl.h>
#endif
namespace {
@@ -157,6 +161,40 @@ int main(int argc, char *argv[])
}
#endif
#ifndef _WIN32
// Tie this worker's lifetime to the daemon's. If the daemon (our parent)
// dies WITHOUT cleaning us up — i.e. it crashes — make sure we don't linger
// as an orphaned process. This is the explicit replacement for the
// controlling-terminal SIGHUP that setsid() above intentionally detached us
// from: graceful shutdown still kills us per-PID from the daemon, and now a
// daemon *crash* cleans us up too.
{
const pid_t daemon_pid = ::getppid();
#ifdef __linux__
// Kernel-level + immediate. PR_SET_PDEATHSIG is delivered when the
// thread that forked us exits; here that is the daemon's long-lived
// event-loop/io thread, so it only fires on real daemon death.
::prctl(PR_SET_PDEATHSIG, SIGKILL);
#endif
// Race guard: if the daemon died during/just before the setup above, our
// parent has already changed (we've been reparented) — exit now. We
// compare against the daemon's actual pid, NOT pid 1, so a daemon that is
// itself PID 1 (e.g. in a container) is handled correctly.
if (::getppid() != daemon_pid) {
_exit(0);
}
// Portable watchdog (covers platforms without PR_SET_PDEATHSIG, e.g.
// macOS, and backs it up elsewhere): if our parent changes — the daemon
// died and the OS reparented us — exit so we never leak.
std::thread([daemon_pid] {
while (::getppid() == daemon_pid) {
::sleep(1);
}
_exit(0);
}).detach();
}
#endif
ModuleArgs args = parseCommandLineArgs(argc, argv);
if (!args.valid) {
return 1;