fix(test): settle watch step on the last build to avoid stale transition rebuilds#14714
fix(test): settle watch step on the last build to avoid stale transition rebuilds#14714stormslowly wants to merge 7 commits into
Conversation
Add WATCH_DIAG-guarded diagnostics to createWatchStepProcessor and a WASM-only test file that registers many copies of side-effects/issue-7400 to reproduce the step-sync race where a trailing Build event resolves the next step's task, so the runner executes a stale bundle.
📦 Binary Size-limit
🎉 Size decreased by 164.00KB from 66.74MB to 66.58MB (⬇️0.24%) |
Merging this PR will degrade performance by 2.17%
|
| Mode | Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|---|
| ❌ | Simulation | rust@split_chunks |
2.1 ms | 2.2 ms | -2.17% |
Tip
Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.
Comparing test/repro-wasm-watch-issue7400-flaky (b881444) with main (a0d0e77)
Footnotes
-
47 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩
…ebuilds Wait until no rebuild is pending (invalidation after last build) before applying a step's copyDiff, so once(Build) captures the copyDiff-triggered rebuild instead of an extra load-induced transition rebuild carrying previous-step content.
…ebuild Replace once(Build) with a settle window that keeps the last build of the step (arming before copyDiff so a legitimately-pending rebuild like change-event/metadata is still captured). Fixes the quiescence-gate regression that starved metadata of its build.
Why
watchCases/side-effects/issue-7400 > should compile step 1is flaky on WASM CI (example): step 1 runs a bundle that still asserts step 0'sexpect(WATCH_STEP).toEqual("0")whileWATCH_STEPis already"1"→expected '1' to deeply equal '0'.Root cause (reproduced + instrumented). The watch harness synchronizes step transitions with
emitter.once(Build)— it takes the first Build aftercopyDiff. Under heavy concurrent compilation load (WASM is slower, so the window is wider), watchpack emits an extra rebuild during the step transition that still carries the previous step's content;oncecaptures that stale rebuild, and the runner executes the wrong bundle. The real, copyDiff-triggered rebuild lands just after.Evidence from an instrumented reproduction: stale runs had exactly 3 builds for the step (vs 2 when healthy); the captured build's hash carried step-0 content while the correct build landed later; reproduced 46/500 under amplified load, 0/150 without. Native is always clean because the transition rebuild drains before the next step.
This is harness fragility, not a watcher bug — the compiler rebuilds correctly;
once(Build)just cannot tell the step's rebuild from an extra one.What
Wait for the last build the step settles on instead of the first: keep resetting a
settleWindow(> the watcheraggregateTimeout) on every Build and resolve once no new build has arrived. The final build always reflects the current sources, so a stale transition rebuild is superseded by the real one. The listener is armed beforecopyDiff, so a rebuild a step legitimately relies on that is already pending (e.g. thechange-event/metadatacase, whose plugin touches a file to trigger a rebuild) is still captured.Change is confined to
createWatchStepProcessor. Validated green across Linux / macOS / Windows / WASM, includingchange-event/metadata, withSTALE=0under both moderate and extreme load in the instrumented reproduction.