Repository navigation
fix(plugins): compile bundles in a child process so a wedged Bun.build cannot hang the server or CI - #1144
Merged
Conversation
…d cannot hang the server or CI The v1.58.1 publish died at exit 137: plugin_validate sat at its 60s test timeout with bun idle in ep_poll and zero child processes, every later compile queued up behind it, and the CI hang watchdog killed the run at 170s. In-process Bun.build with JS plugin callbacks can park the single bundle thread inside the in-flight build's MiniEventLoop — fixed upstream in oven-sh/bun 35060 and 42680, neither in the pinned bun 1.3.11 — and in production the same wedge would hang a real install, validate, or reload RPC forever. buildPluginBundles now spawns plugin-build-child.adapter.ts, the same execPath-plus-script-path pattern as the plugin server child, with a 20s deadline, one fresh-process retry, and a marker-line JSON verdict on stdout read on the close event so the pipe is drained before settling. A wedged bundler becomes a clean ok:false after the deadline instead of a hung process, and the isolation costs nothing: a child compile of the hello fixture measures 100-200ms where the tests used to budget 60s. The deadline path is pinned by a stalling-child fixture test. Also widens the dispose gracefulTimeoutMs upper bound in agent.test.ts from 500ms to 5s: a 100ms timer measured against a 500ms wall-clock ceiling fails under ordinary machine load (observed 731ms), which is scheduler noise, not the promise under test — dispose proceeding instead of waiting forever is proven by the lower bound and by resolving at all.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The failure this fixes
The v1.58.1 publish (run 35817749252) died with exit 137: the CI hang watchdog SIGKILLed
bun testafter 170 s of silence. The diagnostics it captured tell the story precisely:plugin_validate > accepts the hello fixture without installing itfailed at its 60 s test timeout — the compile never finishedep_pollwith zero child processes — nothing external was stuck; the bundler wedged in-processRoot cause
buildPluginBundlesranBun.buildin-process with JS plugin callbacks (onResolve/onLoadfor the host-module rewrite). On bun ≤ 1.3.x allBun.build()calls serialize behind a single bundle thread, which can park inside the in-flight build's MiniEventLoop waiting on plugin work — fixed upstream in oven-sh/bun#35060 and oven-sh/bun#42680, neither in the pinned bun 1.3.11. Once one build parks, every later compile queues behind it forever. In production the identical wedge would hang a real install / validate / reload RPC with no way out.Fix — process isolation, the pattern the plugin system already uses
buildPluginBundlesnow spawnsplugin-build-child.adapter.ts(sameprocess.execPath+ script-path shape asspawnPluginChild; the package shipssrc/server/so the child runs from source):{ok:false}with the child's stderr tail, never a hangcloseevent so the pipe is fully drained before settlingbuildPluginBundlesInProcessstays exported only for the child; public callers (plugin-serviceinstall,plugin_validate) are unchangedstalling-build-childfixture testCLAUDE.md's Plugin System section records the incident, the upstream bug, and why the compile must not move back in-process.
Also: one wall-clock flake defused
agent.test.tsdispose() resolves after gracefulTimeoutMs even if closed never resolvesassertedelapsed < 500around a 100 ms timer — observed at 731 ms under ordinary load, failing the suite twice while validating this change. The ceiling is now 5 s: the promise (dispose proceeds instead of waiting forever) is proven by the ≥ 90 ms bound and by resolving at all; a sub-second ceiling was measuring the scheduler.Validation
bun run test— 8379 pass / 0 fail (623 files)bun run typecheck,lint,lint:comments,check:arch,check:commits— cleanCI noise scoreboard for today