Recover stale supervised daemons
Teach daemon stop to use lifecycle shutdown when the API is reachable even if the owner pid is stale, then wait for the API to disappear and clean the stale pidfile. Launch desktop-managed daemons detached from desktop stdio, preserve stale reachable daemon ownership for version checks, and allow desktop stop/restart to use the CLI recovery path. Add supervisor heartbeats so supervised workers shut down when their supervisor disappears instead of surviving as orphaned reachable daemons.
This commit is contained in:
@@ -19,12 +19,28 @@ type SupervisorLifecycleMessage =
|
||||
reason?: string;
|
||||
};
|
||||
|
||||
interface SupervisorHeartbeatMessage {
|
||||
type: "paseo:supervisor-heartbeat";
|
||||
}
|
||||
|
||||
interface BootstrapResult {
|
||||
paseoHome: string;
|
||||
logger: ReturnType<typeof createRootLogger>;
|
||||
config: ReturnType<typeof loadConfig>;
|
||||
}
|
||||
|
||||
function isPidAlive(pid: number): boolean {
|
||||
try {
|
||||
process.kill(pid, 0);
|
||||
return true;
|
||||
} catch (err) {
|
||||
if (typeof err === "object" && err !== null && "code" in err && err.code === "EPERM") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function bootstrapFromEnvironment(): BootstrapResult {
|
||||
try {
|
||||
const paseoHome = resolvePaseoHome();
|
||||
@@ -150,6 +166,46 @@ async function main() {
|
||||
beginShutdown("restart lifecycle intent", { successExitCode: 0 });
|
||||
};
|
||||
|
||||
const installSupervisorLivenessGuard = () => {
|
||||
if (typeof process.send !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
const supervisorPid = process.ppid;
|
||||
let lastSupervisorHeartbeatAt = Date.now();
|
||||
let supervisorShutdownRequested = false;
|
||||
const requestSupervisorShutdown = (reason: string) => {
|
||||
if (supervisorShutdownRequested) {
|
||||
return;
|
||||
}
|
||||
supervisorShutdownRequested = true;
|
||||
beginShutdown(reason);
|
||||
};
|
||||
|
||||
process.on("message", (message: unknown) => {
|
||||
if (
|
||||
typeof message === "object" &&
|
||||
message !== null &&
|
||||
"type" in message &&
|
||||
(message as SupervisorHeartbeatMessage).type === "paseo:supervisor-heartbeat"
|
||||
) {
|
||||
lastSupervisorHeartbeatAt = Date.now();
|
||||
}
|
||||
});
|
||||
process.on("disconnect", () => requestSupervisorShutdown("supervisor disconnect"));
|
||||
|
||||
const timer = setInterval(() => {
|
||||
const ipcConnected = typeof process.connected === "boolean" ? process.connected : true;
|
||||
const heartbeatExpired = Date.now() - lastSupervisorHeartbeatAt > 3500;
|
||||
if (ipcConnected === false || !isPidAlive(supervisorPid) || heartbeatExpired) {
|
||||
requestSupervisorShutdown("supervisor disconnect");
|
||||
}
|
||||
}, 1000);
|
||||
timer.unref();
|
||||
};
|
||||
|
||||
installSupervisorLivenessGuard();
|
||||
|
||||
try {
|
||||
daemon = await createPaseoDaemon(
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user