Skip to content

innerModuleLoading: decide needsErrorReaction from the request key, not loadedModules size - #363

Open
robobun wants to merge 1 commit into
mainfrom
farm/0ad637d2/innermoduleloading-key-check
Open

innerModuleLoading: decide needsErrorReaction from the request key, not loadedModules size#363
robobun wants to merge 1 commit into
mainfrom
farm/0ad637d2/innermoduleloading-key-check

Conversation

@robobun

@robobun robobun commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Problem

ASSERTION FAILED: needsErrorReaction != module->loadedModules().contains(ModuleMapKey { request.m_specifier.impl(), request.type() }) in JSModuleLoader::innerModuleLoading (JSModuleLoader.cpp:893).

Seen as a fleet escalation (5 hits) plus ~17% reproducible under 12-way parallel load with a package-resolve fuzzer scenario that dynamic-imports a graph over a freshly rewritten package tree while calling multiple resolver entry points. Debug/asserts builds abort; release builds do not crash.

Release consequence

innerModuleLoading decides whether to attach the ModuleGraphLoadingError reaction to the promise hostLoadImportedModule returned by checking whether module->loadedModules().size() grew across the call. When it grows, the code assumes FinishLoadingImportedModule ran synchronously with a normal completion for this request, so the edge is already accounted for and the error reaction can be skipped.

That inference is wrong when the size growth came from a different (specifier, type) key of the same referrer. A nested graph walk reached via FinishLoadingImportedModule -> ContinueModuleLoading/ContinueDynamicImport can visit this same referrer (still status == New) and populate one of its other request keys synchronously. size() grows by exactly one, so needsErrorReaction becomes false, but the current request's load promise is still pending. If that promise later rejects, state.[[PromiseCapability]] never hears about it: the dynamic import hangs and the module error is swallowed.

Fix

Replace the size heuristic with a direct key lookup: needsErrorReaction = !module->loadedModules().contains(requestKey). This is exactly what the assertion already computed in debug builds, so it is provably equivalent when the invariant holds and correct when it does not. The same HashMap::find already runs two statements earlier for the 2.d.ii cache hit, so the cost is one extra contains() on the new-edge path.

The size() <= before + 1 and the needsErrorReaction != contains() asserts are removed: the first is no longer an invariant this code depends on, and the second is now a tautology.

Verification

  • Built locally (debug-local, ASAN on) against bun main at 4eb6f99c1.
  • import-meta.test.js (32 tests) passes.
  • The repro scenario runs clean (12-way parallel, 25 iterations each).

A local-only deterministic reproduction proved intractable without the original fuzzer artifact (repro/modulegraph-assert-1206/p-resolve-standalone.ts); every synchronous re-entrancy candidate path was guarded in a static read of the code, yet the assert fires under load. The fix is correct by construction for the described failure mode and carries no behavioral downside when the invariant holds.

…ot loadedModules size

The size() == sizeBefore heuristic assumes that the only mutation
hostLoadImportedModule can make to referrer.[[LoadedModules]] is adding
the current request. Under load that invariant does not hold: a nested
graph walk reached through FinishLoadingImportedModule can visit the
same referrer (still status New) and synchronously populate a different
request key first. size() grows by 1, so needsErrorReaction becomes
false, but the current request is still pending; the
ModuleGraphLoadingError reaction is never attached, and a later
rejection of that load promise never reaches state.[[PromiseCapability]],
leaving a dynamic import hanging or a module error swallowed.

Check the request key directly instead of inferring from size(). The
same lookup already runs two statements earlier for the 2.d.ii cache
hit, so the cost is one extra HashMap::contains on the new-edge path.
Drop the now-tautological assert.
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 9 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 656c4ec3-8e73-4487-a3d1-d6a3991da650

📥 Commits

Reviewing files that changed from the base of the PR and between 9839239 and bae508b.

📒 Files selected for processing (1)
  • Source/JavaScriptCore/runtime/JSModuleLoader.cpp

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

Preview Builds

Commit Release Date
bae508b2 autobuild-preview-pr-363-bae508b2 2026-07-27 23:31:48 UTC

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.

2 participants