fix(server): make stale lock recovery upgrade-safe

Only heartbeat-capable locks age out by default. Desktop-managed startup may reclaim legacy locks after its reachability probe, while supervisors shut down if heartbeat detects ownership loss.
This commit is contained in:
Mohamed Boudra
2026-07-15 14:45:56 +02:00
parent fb4a686ba0
commit dafb486819
4 changed files with 242 additions and 53 deletions
@@ -110,6 +110,7 @@ async function main(): Promise<void> {
try {
await acquirePidLock(paseoHome, null, {
ownerPid: process.pid,
reclaimLegacyDesktopLock: process.env.PASEO_DESKTOP_MANAGED === "1",
});
} catch (error) {
if (error instanceof PidLockError) {
@@ -121,8 +122,16 @@ async function main(): Promise<void> {
}
let lockReleased = false;
let requestSupervisorShutdown: ((reason: string) => void) | null = null;
const stopLockHeartbeat = startPidLockHeartbeat(paseoHome, {
ownerPid: process.pid,
onError: (error) => {
const message = error instanceof Error ? error.message : String(error);
process.stderr.write(`PID lock heartbeat failed: ${message}\n`);
if (error instanceof PidLockError) {
requestSupervisorShutdown?.("pid_lock_ownership_lost");
}
},
});
const releaseLock = async (): Promise<void> => {
if (lockReleased) {
@@ -135,7 +144,7 @@ async function main(): Promise<void> {
});
};
runSupervisor({
const supervisor = runSupervisor({
name: "DaemonRunner",
startupMessage: "Starting daemon worker (IPC restart and crash restart enabled)",
resolveWorkerEntry: () => workerEntry,
@@ -164,6 +173,7 @@ async function main(): Promise<void> {
},
onSupervisorExit: releaseLock,
});
requestSupervisorShutdown = supervisor.requestShutdown;
}
void main().catch((error) => {
+7 -1
View File
@@ -47,6 +47,10 @@ interface SupervisorOptions {
logFile?: SupervisorLogFileOptions;
}
export interface SupervisorController {
requestShutdown(reason: string): void;
}
function describeExit(code: number | null, signal: NodeJS.Signals | null): string {
return signal ?? (typeof code === "number" ? `code ${code}` : "unknown");
}
@@ -105,7 +109,7 @@ function createSupervisorLogStream(options: SupervisorLogFileOptions | undefined
});
}
export function runSupervisor(options: SupervisorOptions): void {
export function runSupervisor(options: SupervisorOptions): SupervisorController {
const restartOnCrash = options.restartOnCrash ?? false;
const workerArgs = options.workerArgs ?? process.argv.slice(2);
const workerEnv = options.workerEnv ?? process.env;
@@ -331,4 +335,6 @@ export function runSupervisor(options: SupervisorOptions): void {
process.stdout.write(`[${options.name}] ${options.startupMessage}\n`);
writeLifecycleLog(options.startupMessage);
spawnWorker();
return { requestShutdown };
}