Files
openidea/smoke/fixtures/server.cjs
Arnaud Ritti a5db5cdda8 fix(smoke): allow graceful server shutdown before forced cleanup (#2054)
The smoke harness killed the server's process group right after closing its stdin, so the host never got to shut down and tool processes it had spawned in their own groups could be left running. Close now waits for the server's graceful EOF shutdown (bounded at 15 s), then kills the server's own group and every independent tool group it owned, reporting group-kill errors only after cleaning the rest. Covered by `smoke/server.test.ts` (graceful, failed initialize, hung shutdown, signal exit, spawn failure, missing `ps`, coalesced frames, kill errors).

Co-authored-by: arnaud-ritti <[email protected]>
2026-11-08 06:39:34 +08:00

50 lines
1.8 KiB
JavaScript
Executable File

#!/usr/bin/env node
const { spawn } = require("node:child_process");
const { writeFileSync } = require("node:fs");
const { join } = require("node:path");
const home = process.env.OPENIDEA_SERVER_HOME;
const mode = process.env.SMOKE_SERVER_MODE;
const tool = spawn(process.execPath, ["-e", "setInterval(() => {}, 1000)"], {
detached: mode !== "hang-inherited",
stdio: "ignore",
});
const second =
mode === "signal-multiple"
? spawn(process.execPath, ["-e", "setInterval(() => {}, 1000)"], {
detached: true,
stdio: "ignore",
})
: undefined;
writeFileSync(join(home, "pids.json"), JSON.stringify({ server: process.pid, tool: tool.pid, second: second?.pid }));
let buffer = Buffer.alloc(0);
process.stdin.on("data", (chunk) => {
buffer = Buffer.concat([buffer, chunk]);
for (;;) {
const end = buffer.indexOf("\r\n\r\n");
if (end < 0) return;
const length = Number(/Content-Length: (\d+)/i.exec(buffer.subarray(0, end).toString())[1]);
if (buffer.length < end + 4 + length) return;
const request = JSON.parse(buffer.subarray(end + 4, end + 4 + length));
buffer = buffer.subarray(end + 4 + length);
const body = JSON.stringify({
jsonrpc: "2.0",
id: request.id,
...(mode === "reject" ? { error: { message: "initialize failed" } } : { result: {} }),
});
process.stdout.write(`Content-Length: ${Buffer.byteLength(body)}\r\n\r\n${body}`);
}
});
process.stdin.on("end", () => {
writeFileSync(join(home, "eof"), "received");
if (mode.startsWith("hang")) return;
if (mode.startsWith("signal")) return process.kill(process.pid, "SIGTERM");
setTimeout(() => {
tool.once("exit", () => {
writeFileSync(join(home, "shutdown"), "tool reaped");
process.exit(0);
});
tool.kill("SIGKILL");
}, 150);
});
setInterval(() => {}, 1000);