Files
msd-core/tests
Tom Boucher ef30e59860 fix(#4448): stop io.test.cjs's in-process runMain calls from corrupting node:test's own fd-1 IPC (#4452)
tests/io.test.cjs's "review fix: pending-outcome cell lifetime" describe
block deliberately drives runMain()/output() in-process (needed to observe
a cross-invocation state leak) instead of via a subprocess. output() ends
with a raw synchronous fs.writeSync(1, ...) to the real stdout fd, and
Node's --test-isolation=process (default since Node 22) uses that same fd
for the file's own parent-child reporter protocol. The two writes racing
produced an intermittent "Unable to deserialize cloned data" that killed
the whole file — observed twice on next's macOS lane, most recently on the
commit that merged PR #4428 (unrelated to that PR's content; io.test.cjs
isn't part of its diff).

Empirically validated locally (gsd-test can't reach macOS): built a repro
loop running N parallel copies of `node --test tests/io.test.cjs` to
recreate CI-like contention. Baseline: ~13-17% of runs hit the corruption
(12/90, 15/90 across two samples). A first fix attempt wrapped the writes
in captureFdAsync (an await-aware twin of the existing captureFdSync,
added because runMain() defers main() through a microtask chain, so a
synchronous wrap restores before the real write fires) — but captureFdAsync
always forwards to the real fs.writeSync by design (matching captureFdSync's
"never swallow" contract, tests/helpers.cjs, #4306). Re-ran the same loop
against that fix: 15/90, statistically unchanged. Forwarding the write
doesn't stop it from reaching the fd node:test's own IPC also uses.

Replaced it with suppressFdAsync: a narrow, deliberate exception to the
never-swallow contract for a window the caller has verified is fully
controlled (a single runMain() call plus its promise-chain settling,
where nothing else can legitimately need that fd). It records the bytes
for the test's own assertions but never lets them reach the real fd.
Re-ran the loop: 0/300 across three samples (90+120+90), including one
round at 8-way parallelism. Also caught and fixed a real bug surfaced by
the same loop: the new regression assertion checked for compact-JSON
`"error":"x"` but output() pretty-prints, so it failed 100% of runs
deterministically until fixed to parse and check the structured value
instead (io.test.cjs, matching this repo's "assert on structured output,
not raw text" convention).

Co-authored-by: sim <sim@local>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 21:06:48 -04:00
..