Skip to content

fix(test): settle watch step on the last build to avoid stale transition rebuilds#14714

Draft
stormslowly wants to merge 7 commits into
mainfrom
test/repro-wasm-watch-issue7400-flaky
Draft

fix(test): settle watch step on the last build to avoid stale transition rebuilds#14714
stormslowly wants to merge 7 commits into
mainfrom
test/repro-wasm-watch-issue7400-flaky

Conversation

@stormslowly

@stormslowly stormslowly commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Why

watchCases/side-effects/issue-7400 > should compile step 1 is flaky on WASM CI (example): step 1 runs a bundle that still asserts step 0's expect(WATCH_STEP).toEqual("0") while WATCH_STEP is 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 after copyDiff. 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; once captures 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 watcher aggregateTimeout) 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 before copyDiff, so a rebuild a step legitimately relies on that is already pending (e.g. the change-event/metadata case, whose plugin touches a file to trigger a rebuild) is still captured.

Change is confined to createWatchStepProcessor. Validated green across Linux / macOS / Windows / WASM, including change-event/metadata, with STALE=0 under both moderate and extreme load in the instrumented reproduction.

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.
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

📦 Binary Size-limit

Comparing cd1c043 to fix(esm_library): preserve static source-phase external bindings (#14674) by Elecmonkey

🎉 Size decreased by 164.00KB from 66.74MB to 66.58MB (⬇️0.24%)

@codspeed-hq

codspeed-hq Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will degrade performance by 2.17%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

❌ 1 regressed benchmark
✅ 42 untouched benchmarks
⏩ 47 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

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)

Open in CodSpeed

Footnotes

  1. 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.
@stormslowly stormslowly changed the title test: repro WASM watch flaky (issue-7400 step-sync race) fix(test): settle watch step on the last build to avoid stale transition rebuilds Jul 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant