mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-11 02:46:29 +00:00
Start the daemon after a reboot hands its old PID to another process (#5277)
Closes #5257
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env npx tsx
|
||||
|
||||
/**
|
||||
* Regression: a reboot reassigns the supervisor's PID to an unrelated process. The lock
|
||||
* left behind must not make the daemon look like it is running, and must never be treated
|
||||
* as a handle on whatever now holds that PID.
|
||||
*/
|
||||
|
||||
import assert from "node:assert";
|
||||
import { spawn } from "node:child_process";
|
||||
import { existsSync } from "node:fs";
|
||||
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir, uptime } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { runLocalPaseo } from "./helpers/local-cli.ts";
|
||||
|
||||
console.log("=== Daemon lock left behind by a reboot ===\n");
|
||||
|
||||
const paseoHome = await mkdtemp(join(tmpdir(), "paseo-stale-boot-lock-"));
|
||||
const env = {
|
||||
PASEO_HOME: paseoHome,
|
||||
PASEO_LOCAL_SPEECH_AUTO_DOWNLOAD: "0",
|
||||
PASEO_DICTATION_ENABLED: "0",
|
||||
PASEO_VOICE_MODE_ENABLED: "0",
|
||||
};
|
||||
|
||||
// Stands in for the process that inherits the supervisor's PID after a reboot. It records a
|
||||
// signal rather than dying of it, so a late delivery cannot slip past the assertion.
|
||||
const signalMarker = join(paseoHome, "bystander-signalled");
|
||||
const bystander = spawn(
|
||||
process.execPath,
|
||||
[
|
||||
"-e",
|
||||
`process.on("SIGTERM", () => require("node:fs").writeFileSync(${JSON.stringify(signalMarker)}, "SIGTERM"));` +
|
||||
`setTimeout(() => {}, 120_000);`,
|
||||
],
|
||||
{ stdio: "ignore" },
|
||||
);
|
||||
let bystanderExited = false;
|
||||
bystander.once("exit", () => {
|
||||
bystanderExited = true;
|
||||
});
|
||||
assert(bystander.pid, "bystander process should have a pid");
|
||||
|
||||
try {
|
||||
const beforeThisBoot = new Date(Date.now() - uptime() * 1000 - 60 * 60_000);
|
||||
await writeFile(
|
||||
join(paseoHome, "paseo.pid"),
|
||||
JSON.stringify({
|
||||
pid: bystander.pid,
|
||||
startedAt: beforeThisBoot.toISOString(),
|
||||
hostname: "before-reboot",
|
||||
uid: process.getuid?.() ?? 0,
|
||||
listen: "127.0.0.1:6767",
|
||||
desktopManaged: true,
|
||||
heartbeat: true,
|
||||
}),
|
||||
);
|
||||
|
||||
const statusResult = await runLocalPaseo(
|
||||
["daemon", "status", "--home", paseoHome, "--json"],
|
||||
env,
|
||||
);
|
||||
assert.strictEqual(
|
||||
statusResult.exitCode,
|
||||
0,
|
||||
`daemon status should succeed:\nstdout:\n${statusResult.stdout}\nstderr:\n${statusResult.stderr}`,
|
||||
);
|
||||
const status = JSON.parse(statusResult.stdout) as { localDaemon?: string; pid?: number | null };
|
||||
assert.strictEqual(
|
||||
status.localDaemon,
|
||||
"stopped",
|
||||
`daemon left behind by a reboot should read as stopped, got: ${statusResult.stdout}`,
|
||||
);
|
||||
assert.strictEqual(status.pid, null, "status should not report the reused pid as the daemon");
|
||||
console.log("✓ daemon status reports stopped\n");
|
||||
|
||||
const stopResult = await runLocalPaseo(["daemon", "stop", "--home", paseoHome], env);
|
||||
assert.strictEqual(
|
||||
stopResult.exitCode,
|
||||
0,
|
||||
`daemon stop should succeed:\nstdout:\n${stopResult.stdout}\nstderr:\n${stopResult.stderr}`,
|
||||
);
|
||||
assert.strictEqual(
|
||||
existsSync(signalMarker),
|
||||
false,
|
||||
"daemon stop must not signal the process that inherited the pid",
|
||||
);
|
||||
assert.strictEqual(bystanderExited, false, "the process that inherited the pid should survive");
|
||||
console.log("✓ daemon stop leaves the process holding that pid alone\n");
|
||||
} finally {
|
||||
bystander.kill("SIGKILL");
|
||||
await rm(paseoHome, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
console.log("=== Daemon lock left behind by a reboot passed ===");
|
||||
Reference in New Issue
Block a user