The ignoreTerm tool child was TERMed before Node installed its handler when the claim store sits on a fast disk. Candidate fixes the fixture; the patch itself is not committed. Co-Authored-By: Claude Opus 5.5 <[email protected]>
68 lines
3.7 KiB
Diff
68 lines
3.7 KiB
Diff
diff --git a/packages/conversation/tests/fake-pi.mjs b/packages/conversation/tests/fake-pi.mjs
|
|
index b3014f86..a78dfebd 100644
|
|
--- a/packages/conversation/tests/fake-pi.mjs
|
|
+++ b/packages/conversation/tests/fake-pi.mjs
|
|
@@ -615,14 +615,34 @@ const children = [];
|
|
// K2); `forkLoop` forks every 5 ms (K12); `ignoreTerm` survives SIGTERM, so
|
|
// only the kill phase ends it (K3, K10, K11). With `pidLog`, the fork loop
|
|
// appends each child's pid and a `term` line when it gets SIGTERM (K12).
|
|
+//
|
|
+// It resolves only once the child writes its ready byte, which it does after
|
|
+// installing its TERM handler. Node takes 15 to 80 ms to get there, and a
|
|
+// force stop can send TERM sooner (12 ms when the claim store is on a fast
|
|
+// disk). A TERM before the handler kills a child that is meant to ignore it
|
|
+// (#1528).
|
|
function spawnChild({ setsid = false, forkLoop = false, ignoreTerm = false, pidLog = null } = {}) {
|
|
const note = pidLog ? `const note=(s)=>require('node:fs').appendFileSync(${JSON.stringify(pidLog)},s+'\\n');` : "const note=()=>{};";
|
|
- const code = note + (ignoreTerm ? "process.on('SIGTERM',()=>note('term'));" : "") + (forkLoop
|
|
+ const code = note + (ignoreTerm ? "process.on('SIGTERM',()=>note('term'));" : "") + "process.stdout.write('r');" + (forkLoop
|
|
? "const {spawn}=require('node:child_process');setInterval(()=>{try{const c=spawn('sleep',['1000'],{stdio:'ignore'});if(c.pid)note(String(c.pid))}catch{}},5);setInterval(()=>{},1e9)"
|
|
: "setInterval(()=>{},1e9)");
|
|
- const child = spawn(process.execPath, ["-e", code], { stdio: "ignore", detached: setsid });
|
|
+ const child = spawn(process.execPath, ["-e", code], { stdio: ["ignore", "pipe", "ignore"], detached: setsid });
|
|
children.push(child.pid);
|
|
- return child.pid;
|
|
+ return new Promise((resolve, reject) => {
|
|
+ const fail = (why) => {
|
|
+ clearTimeout(timer);
|
|
+ reject(new Error(`tool child ${child.pid} ${why} before it was ready`));
|
|
+ };
|
|
+ const timer = setTimeout(() => fail("took 10 s"), 10000);
|
|
+ child.once("error", (err) => fail(err.code ?? err.message));
|
|
+ child.once("exit", (code, signal) => fail(`exited (${signal ?? code})`));
|
|
+ child.stdout.once("data", () => {
|
|
+ clearTimeout(timer);
|
|
+ child.removeAllListeners("exit");
|
|
+ child.stdout.destroy();
|
|
+ resolve(child.pid);
|
|
+ });
|
|
+ });
|
|
}
|
|
|
|
// K13: a member writes its own pid to another cgroup's cgroup.procs.
|
|
@@ -671,7 +691,7 @@ async function main() {
|
|
drop: () => fake.dropResponse(req.type, req.n ?? 1),
|
|
extension: () => void fake.extensionPrompt(req.args ?? {}),
|
|
state: () => ({ streaming: fake.streaming, runs: fake.runs.length, commands: fake.commands, pid: process.pid, children, appends: fake.appends }),
|
|
- child: () => ({ pid: spawnChild(req.args ?? {}) }),
|
|
+ child: () => spawnChild(req.args ?? {}).then((pid) => ({ pid })),
|
|
escape: () => escape(req.target),
|
|
cgroup: () => readFileSync(`/proc/${req.pid ?? process.pid}/cgroup`, "utf8"),
|
|
waitPaused: () => fake.waitPaused(req.point),
|
|
@@ -679,12 +699,13 @@ async function main() {
|
|
stall: () => void process.stdin.pause(),
|
|
};
|
|
if (!ops[req.op]) return sock.write(encodeLine({ id: req.id, ok: false, error: `unknown op ${req.op}` }));
|
|
+ const failed = (err) => sock.write(encodeLine({ id: req.id, ok: false, error: String(err.message) }));
|
|
try {
|
|
const out = ops[req.op]();
|
|
- if (out && typeof out.then === "function") out.then(reply);
|
|
+ if (out && typeof out.then === "function") out.then(reply, failed);
|
|
else reply(out ?? null);
|
|
} catch (err) {
|
|
- sock.write(encodeLine({ id: req.id, ok: false, error: String(err.message) }));
|
|
+ failed(err);
|
|
}
|
|
});
|
|
sock.on("data", (c) => splitter.push(c));
|