Skip to content

module-loader: don't double-fire moduleRegistryModuleSettled after inline sync replay#225

Merged
alii merged 1 commit into
mainfrom
claude/webkit-30493-fix
May 12, 2026
Merged

module-loader: don't double-fire moduleRegistryModuleSettled after inline sync replay#225
alii merged 1 commit into
mainfrom
claude/webkit-30493-fix

Conversation

@alii
Copy link
Copy Markdown
Member

@alii alii commented May 12, 2026

Rebased cherry-pick of #217 onto current main (so it includes #215).

Fixes oven-sh/bun#30493, oven-sh/bun#30281.

Adds a modulePromise->status() != Pending early-return guard to moduleRegistryModuleSettled, symmetric with the guard already present in moduleRegistryFetchSettled. See #217 for the full root-cause writeup.

Verified locally against the oven-sh/bun#30493 repro:

Supersedes #217 (same patch, rebased).

…line sync replay

hostLoadImportedModule's synchronous-replay path (JSModuleLoader.cpp
branch `fetchPromise is Fulfilled`, line 712-725) calls makeModule +
fetchComplete + modulePromise->fulfillPromise inline while a
require(esm) drains the synchronous module queue.

If a ModuleRegistryFetchSettled reaction had already run on the
*normal* microtask queue for that same entry before the require(esm)
entered sync mode, it left a ModuleRegistryModuleSettled reaction
queued there too. When the normal queue later drained, that stale
reaction re-entered fetchComplete on an entry whose status was now
Fetched, tripping the "m_status == Fetching" assertion at
ModuleRegistryEntry.cpp:254 (SIGABRT on Linux, arm64 PAC IB trap /
SIGTRAP on macOS). moduleRegistryFetchSettled already guards this
with `if (modulePromise->status() != Pending) return;` — apply the
same guard symmetrically to moduleRegistryModuleSettled.

Fixes oven-sh/bun#30281.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 12, 2026

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5276c358-2f52-41d5-bfc6-d3ad58f9489d

📥 Commits

Reviewing files that changed from the base of the PR and between 88b2f7a and 2b6b1c3.

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

Walkthrough

This change adds a pending-promise guard to moduleRegistryModuleSettled that mirrors existing protection in moduleRegistryFetchSettled. The guard prevents the queued microtask reaction from re-entering settlement logic when the module promise has already been fulfilled inline via synchronous host-load replay.

Changes

Module Settlement Re-entrancy Prevention

Layer / File(s) Summary
Pending Promise Guard
Source/JavaScriptCore/runtime/JSMicrotask.cpp
Inside moduleRegistryModuleSettled, adds a USE(BUN_JSC_ADDITIONS) check to return early when modulePromise is no longer Pending, avoiding duplicate settlement from the queued reaction path after synchronous replay.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main fix: preventing double-firing of moduleRegistryModuleSettled after inline sync replay, which matches the core change in the pull request.
Description check ✅ Passed The PR description includes the bug references (oven-sh/bun#30493, oven-sh/bun#30281), technical explanation, verification steps, and context, though it does not follow the WebKit Bugzilla template provided in the description_template.
Linked Issues check ✅ Passed The PR directly addresses the root cause of issue #30493 (deadlock when requiring ESM with diamond dependency through barrel) by adding a guard to prevent stale moduleRegistryModuleSettled reactions from re-entering fetchComplete on already-fetched entries.
Out of Scope Changes check ✅ Passed All changes are limited to the specific fix scope: adding a pending-status guard to moduleRegistryModuleSettled, mirroring the existing guard in moduleRegistryFetchSettled, with no extraneous modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

@github-actions
Copy link
Copy Markdown

Preview Builds

Commit Release Date
2b6b1c39 autobuild-preview-pr-225-2b6b1c39 2026-05-12 00:52:52 UTC

@alii alii merged commit 5488984 into main May 12, 2026
43 checks passed
Jarred-Sumner pushed a commit to oven-sh/bun that referenced this pull request May 12, 2026
…0527)

## WebKit changes (88b2f7a2 → 5488984d)

Single commit on top of the previous pin:

### `module-loader: don't double-fire moduleRegistryModuleSettled after
inline sync replay` (oven-sh/WebKit#225, rebased #217)

**File:** `Source/JavaScriptCore/runtime/JSMicrotask.cpp`

**What:** Adds a `modulePromise->status() != Pending` early-return guard
to `moduleRegistryModuleSettled`, symmetric with the guard already
present in `moduleRegistryFetchSettled`. Gated under `#if
USE(BUN_JSC_ADDITIONS)`.

**Why:** `require()` of an ESM whose graph contained a diamond
dependency through a barrel deadlocked (release) / aborted on `ASSERTION
FAILED: m_status == Status::Fetching` (debug).
`hostLoadImportedModule`'s synchronous-replay branch (taken when
`require(esm)` is draining the synchronous module queue) calls
`fetchComplete` + fulfills `modulePromise` inline. If a
`ModuleRegistryFetchSettled` reaction had already run on the *normal*
microtask queue for the same entry before sync mode was entered, it left
a stale `ModuleRegistryModuleSettled` reaction queued there. When the
normal queue later drained, that reaction re-entered `fetchComplete` on
an already-`Fetched` entry.

No changes to `JSType.h`. No WebCore code-generator changes.

---

**Verification:**
- ✅ `test/regression/issue/30493.test.ts` fails on current `main`
(assertion crash, empty stdout)
- ✅ Same test passes on `bun run build:local` with the patched WebKit
- ✅ Same test passes on `bun bd` with the prebuilt preview tarball
- ✅ Full bun CI green against `autobuild-preview-pr-225-2b6b1c39` (build
#53556 — 67 pass, 3 pre-existing main flakes also red on #30522)

Fixes #30493
Fixes #30281
Closes #30283 (the dependency-free 6-file repro in this PR covers the
same root cause without needing a react+MUI install)

---------

Co-authored-by: robobun <117481402+robobun@users.noreply.github.com>
alii added a commit to oven-sh/bun that referenced this pull request May 12, 2026
… module no longer hangs (#30537)

A second `await import()` of a path whose first import threw used to
hang forever instead of re-throwing. Two reported variants:

- **#23139**: same path twice
- **#22743**: path X, then a *different* error path Y, then X again
(true regression, 1.2.20 → 1.2.21)

While verifying #30527 I checked whether oven-sh/WebKit#225 covered
these — turns out both were **already fixed** before that bump (main +
WebKit `88b2f7a2` doesn't hang either). The fix landed somewhere in the
module-loader rewrite window (#29393#30262); the issues just never
got re-tested.

| build | #23139 | #22743 |
|---|---|---|
| 1.3.13 | hangs | hangs |
| main + WebKit `88b2f7a2` (pre-#30527) | ✅ | ✅ |
| main + WebKit `5488984d` | ✅ | ✅ |

Both tests fail on `USE_SYSTEM_BUN=1` (stdout missing the post-hang
lines, killed by spawn timeout) and pass on `bun bd test`.

Fixes #23139
Fixes #22743
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.

require() of ESM module with diamond dependency through barrel deadlocks

1 participant