From acac0b865ea74d2aa6928f35c6b1c8bade99eee2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 5 Sep 2026 13:33:25 +0200 Subject: [PATCH] test: synchronize stdin lifecycle input with child readiness --- changelog.d/9783-stdin-fixture-handshake.md | 3 ++ ...t_gap_9676_stdin_unref_ref_keeps_reader.ts | 32 +++++++++++++------ 2 files changed, 25 insertions(+), 10 deletions(-) create mode 100644 changelog.d/9783-stdin-fixture-handshake.md diff --git a/changelog.d/9783-stdin-fixture-handshake.md b/changelog.d/9783-stdin-fixture-handshake.md new file mode 100644 index 0000000000..e2dafb7879 --- /dev/null +++ b/changelog.d/9783-stdin-fixture-handshake.md @@ -0,0 +1,3 @@ +Make the stdin lifecycle parity fixture wait for each child to finish its toggle +and GC churn before sending the second input chunk. Removing the four fixed +2.5-second waits lets the Node oracle finish within the suite's 10-second budget. diff --git a/test-files/test_gap_9676_stdin_unref_ref_keeps_reader.ts b/test-files/test_gap_9676_stdin_unref_ref_keeps_reader.ts index 2a11c08adc..dc280056f2 100644 --- a/test-files/test_gap_9676_stdin_unref_ref_keeps_reader.ts +++ b/test-files/test_gap_9676_stdin_unref_ref_keeps_reader.ts @@ -76,6 +76,10 @@ function runRole(name: string, onFirst: (s: any) => void, doChurn: boolean): voi console.log(name + " phase1: true"); onFirst(s); if (doChurn) console.log(name + " churn: " + (churn(300000) > 0)); + // The parent sends TWO only after the toggle and all churn complete. + // A fixed delay cannot prove this ordering and four sequential 2.5s + // waits alone exceed the parity suite's 10s per-process budget (#9783). + console.log(name + " ready: true"); } else if (phase === 1 && text.indexOf("TWO") >= 0) { clearInterval(ticker); finish(name + " phase2: true"); @@ -110,9 +114,24 @@ if (role === "unref-ref") { new Promise((resolve) => { const child = spawn(process.execPath, childArgs, { env: { ...process.env, [ROLE_ENV]: name }, - stdio: ["pipe", "inherit", "inherit"], + stdio: ["pipe", "pipe", "inherit"], }); let settled = false; + let output = ""; + let sentSecond = false; + child.stdout!.on("data", (chunk: any) => { + process.stdout.write(chunk); + output += String(chunk); + // stdout may split the readiness line across arbitrary chunks. + if (!sentSecond && output.includes(name + " ready: true\n")) { + sentSecond = true; + try { + child.stdin!.write("TWO\n"); + } catch { + /* child already gone */ + } + } + }); const watchdog = setTimeout(() => { if (settled) return; settled = true; @@ -120,7 +139,8 @@ if (role === "unref-ref") { child.kill("SIGKILL"); resolve(); }, WATCHDOG_MS); - child.on("exit", (code) => { + // Drain the piped stdout before printing the role's exit summary. + child.on("close", (code) => { if (settled) return; settled = true; clearTimeout(watchdog); @@ -134,14 +154,6 @@ if (role === "unref-ref") { /* child already gone */ } }, 120); - // Late enough that the churn role has finished collecting first. - setTimeout(() => { - try { - child.stdin!.write("TWO\n"); - } catch { - /* child already gone */ - } - }, 2500); }); (async () => {