openclaw / src /cli /gateway-cli /shutdown-hard-exit.process.test.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
f778c12 verified
Raw History Blame Contribute Delete
3.02 kB
// Process-boundary proof that the hard-exit watchdog survives main-thread starvation.
import { spawnSync } from "node:child_process";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { expect, it } from "vitest";
const WATCHDOG_DELAY_MS = 100;
const CHILD_TIMEOUT_MS = 3_000;
const watchdogModuleUrl = pathToFileURL(
path.resolve("src/cli/gateway-cli/shutdown-hard-exit.ts"),
).href;
function runWatchdogChild() {
const script = `
import { writeSync } from "node:fs";
import { armShutdownHardExitWatchdog } from ${JSON.stringify(watchdogModuleUrl)};
const fail = (message) => {
writeSync(2, \`error:\${message}\\n\`);
process.exit(2);
};
const firstWatchdog = armShutdownHardExitWatchdog({
delayMs: ${WATCHDOG_DELAY_MS},
onError: (error) => fail(String(error)),
});
if (firstWatchdog === null) {
fail("first-watchdog-not-armed");
}
firstWatchdog.cancel();
await new Promise((resolve) => setTimeout(resolve, ${WATCHDOG_DELAY_MS * 2}));
writeSync(1, "cancelled-watchdog-deadline-survived\\n");
process.once("beforeExit", () => {
// Reaching beforeExit proves the cancelled worker no longer retains process liveness.
writeSync(1, "cancelled-watchdog-released-liveness\\n");
const secondWatchdog = armShutdownHardExitWatchdog({
delayMs: ${WATCHDOG_DELAY_MS},
onError: (error) => fail(String(error)),
});
if (secondWatchdog === null) {
fail("second-watchdog-not-armed");
}
writeSync(1, "second-watchdog-armed\\n");
while (true) {}
});
`;
const startedAt = Date.now();
const result = spawnSync(
process.execPath,
["--import", "tsx", "--input-type=module", "--eval", script],
{
cwd: path.resolve("."),
encoding: "utf8",
env: { ...process.env, VITEST: undefined },
killSignal: "SIGKILL",
timeout: CHILD_TIMEOUT_MS,
},
);
return { elapsedMs: Date.now() - startedAt, result };
}
it("cancels one watchdog before another kills a blocked main thread", () => {
const { elapsedMs, result } = runWatchdogChild();
const diagnostics = JSON.stringify(
{
elapsedMs,
status: result.status,
signal: result.signal,
error: result.error ? String(result.error) : undefined,
stdout: result.stdout,
stderr: result.stderr,
},
null,
2,
);
const markers = [
"cancelled-watchdog-deadline-survived",
"cancelled-watchdog-released-liveness",
"second-watchdog-armed",
];
let previousMarkerIndex = -1;
for (const marker of markers) {
const markerIndex = result.stdout.indexOf(marker);
expect(markerIndex, diagnostics).toBeGreaterThan(previousMarkerIndex);
previousMarkerIndex = markerIndex;
}
expect(result.error, diagnostics).toBeUndefined();
expect(result.status, diagnostics).toBeNull();
expect(result.signal, diagnostics).toBe("SIGKILL");
expect(elapsedMs, diagnostics).toBeLessThan(CHILD_TIMEOUT_MS);
});