next.js
39b7da2e - Turbopack: terminate failed plugin worker threads (#96592)

Commit
6 days ago
Turbopack: terminate failed plugin worker threads (#96592) ## What Terminate a worker-thread plugin runtime when an evaluation or IPC error marks its `WorkerOperation` non-reusable. `disallow_reuse()` already removed the worker from pool statistics and disabled the callback that returns it to the pool, but it did not stop the underlying Node.js `Worker`. The worker then stayed strongly owned by `loaderWorkers`, kept its V8 isolate/module graph alive, and waited forever for another task that could never arrive. This calls the existing worker terminator used by `wait_or_kill`, scale-down, and scale-to-zero. It removes the routed native channel and dispatches the existing JavaScript `Worker.terminate()` callback, which also deletes the map entry. ## Why this matters for v0 v0's planned worker-thread runtime turns transient loader/PostCSS errors during agent edits into a deterministic lifecycle leak. On the real v0 `/button` route, each unique Tailwind/PostCSS failure retained one complete worker runtime on the baseline. The balanced eight-process A/B panel (80 unique failures per lane) measured: | Lane | `WorkerThread` slope | Private-memory slope | Median failure | Median recovery | >5 s failures | |---|---:|---:|---:|---:|---:| | baseline | **+1.000/error** | **+49.81 MiB/error** | 108.04 ms | 329.26 ms | 3 / 80 | | candidate | **0.000/error** | **+5.12 MiB/error** | 108.15 ms | 317.65 ms | 2 / 80 | That is a **100% elimination of leaked-worker growth** and an **89.7% reduction in retained private-memory slope** (exact process-bootstrap 95% interval: 85.7%–99.5%). Every candidate process was below every baseline process; exact 4-vs-4 process-label permutation `p=0.0143` one-sided / `0.0286` two-sided. Median failure latency changed by only +0.11 ms. The independent 100-cycle-per-lane endurance confirmation showed the same causal result: | Lane | `WorkerThread` cycle 1→100 | Private memory cycle 1→100 | Private slope | Median failure | Median recovery | |---|---:|---:|---:|---:|---:| | baseline | **38→137** | **4.28→7.34 GiB** | **+27.02 MiB/error** | 104.45 ms | 331.32 ms | | candidate | **32→32** | **3.93→4.10 GiB** | **+0.98 MiB/error** | 102.94 ms | 297.24 ms | That long run eliminated all observed worker growth and reduced the fitted private-memory slope 96.4%. Every one of the 200 unique failures recovered to the complete real route, and the source hash was restored after every cycle. The benchmark used fresh `.next` state per process, five error/recovery warmups, a five-minute quiescence window, unique uncached errors, exact source restoration after every cycle, and within-process slopes. Baseline and candidate native binaries were built from the same Next.js base, with only this lifecycle change affecting candidate native code, and selected by SHA-verified `NEXT_TEST_NATIVE_DIR` paths. This is independent of #96433. That PR coordinates explicit multi-file edit transactions; this PR fixes the lifecycle of a worker that has already failed. It also does not change idle-worker scale-down policy: failed workers are absent from the idle pool, so scale-down cannot see them. ## Regression test The new development test schedules a one-second filesystem marker inside a custom loader, then throws an evaluation error under `turbopackPluginRuntimeStrategy: 'workerThreads'`: - baseline native binary: fails because the orphan worker remains alive and writes the marker; - candidate native binary: passes because the worker is actually terminated; - candidate then renders a recovered value, proving replacement/recovery works. ## Validation - `pnpm build-all` - `cargo fmt --all -- --check` - `cargo check -p turbopack-node --all-targets` - `cargo test -p turbopack-node --lib` - `cargo clippy -p turbopack-node --all-targets -- -D warnings` - focused Prettier and ESLint checks for the new test - candidate integration test pass; exact baseline integration test fails at the intended liveness assertion - GPT-5.6 Sol xhigh + Claude Opus 5 xhigh autoreview panel: zero actionable findings, patch correct at 0.96 confidence Exact optimized native SHA-256 values used by the benchmark: - baseline: `609eeeed41e1425f06136b54b7997e5b1b1add992169b17a797889dfb830dd7f` - candidate: `141211112ae47b8bd814aaab437060114e92f60a1bf6f23e1b0e153b1e4837cc` Both stripped binaries were 205,863,784 bytes. Raw per-process JSON, `/proc` samples, server logs, harness source, analysis output, and A/B plus endurance graphs are retained in the investigation archive. Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
Author
Parents
Loading