Module loader: a removed module whose in-flight load fails no longer poisons later imports of it (WebKit bump for oven-sh/WebKit#474) - #39711
Conversation
|
Warning Review limit reachedYour included review limit has been reached. You’re in a promotional period — use the checkbox below to run this review for free:
On-demand reviews are free for the next 31 days. After that, they cost $0.25 per reviewed file. How can I continue?Run this review now using the option above, or comment You can also wait for the limit to reset (next review available in 15 minutes), then comment An organization admin can change what happens after included review limits in Billing. How do review limits work?CodeRabbit enforces per-developer PR review limits within each organization. For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (5)
Comment |
|
Status: engine fix in oven-sh/WebKit#474 (preview Reproduced with the five removal fixtures in CI on Next step is not in this PR: once oven-sh/WebKit#472 and #474 merge and #39674 moves its pin, |
|
The review note on |
253e70c to
b3bddca
Compare
|
Updated 8:39 AM PT - Aug 28th, 2026
❌ @robobun, your commit 54f5bd2 has 1 failures in
🧪 To try this PR locally: bunx bun-pr 39711That installs a local version of the PR into your bun-39711 --bun |
|
Restacked: this PR is now based on #39674 (the Bun side of oven-sh/WebKit#472), mirroring oven-sh/WebKit#474 on #472, so the diff is the pin move plus the tests. The failure-shape tests now extend #39674's fixture instead of duplicating it, and three tests were added for the part of the engine change that is visible without |
662c961 to
5b290db
Compare
|
The conflict came from the base moving: #39674 was rebased onto current main (its pin is now the oven-sh/WebKit#472 preview rebased onto b7f217b4), and oven-sh/WebKit#472 itself was rebased, so oven-sh/WebKit#474 is rebased on top of it (same two commits, files unchanged) and this branch is rebased onto the new #39674 head. The only conflict was the |
b3bddca to
0ff6fbc
Compare
|
Rebased and pushed. oven-sh/WebKit#474 now sits on the rebased oven-sh/WebKit#472 (its two commits apply unchanged, all five engine files are byte-identical to the reviewed version), this branch sits on the rebased #39674, and the pin is the new #474 preview |
|
Re-checked after a second conflict notice: nothing to resolve. The base (#39674's branch) is still at |
dd6015e to
c45fe34
Compare
0ff6fbc to
ed28db8
Compare
|
Rebased again: #39674 (the base) was rebased onto current main, so this branch was rebased onto its new head ( |
c45fe34 to
f2b3d94
Compare
ed28db8 to
b8699f8
Compare
|
Rebased onto the new head of #39674 ( |
f2b3d94 to
c763233
Compare
|
Same cause as before: the base moved. oven-sh/WebKit#472 was rebased onto the current fork main ( |
b8699f8 to
6290823
Compare
|
Pushed. oven-sh/WebKit#474 is re-applied on the rebased #472 (its diff is unchanged), its preview |
c763233 to
f4e43ed
Compare
|
Bot review of |
8aedcf7 to
6e4ce9b
Compare
|
The base moved again (oven-sh/WebKit#472 rebased onto fork main |
a5463c3 to
e231611
Compare
|
Pushed. oven-sh/WebKit#474 is re-applied on the rebased #472 (identical diff), its preview |
6e4ce9b to
86e448e
Compare
|
The base moved again (oven-sh/WebKit#472 rebased onto fork main |
e231611 to
63fb669
Compare
|
Pushed. oven-sh/WebKit#474 is re-applied on the rebased #472 (identical diff), its preview |
|
Nothing to rebase on this side yet: this PR is mergeable against its base (#39674 at |
|
Bot review of |
86e448e to
b067a21
Compare
|
The base moved again (#39674 now on the oven-sh/WebKit#472 preview at |
b067a21 to
a96449c
Compare
|
Holding the push. oven-sh/WebKit#474 now sits on the #472 head that includes the 6b879687ee upgrade, and its preview ( |
a96449c to
e6fa177
Compare
|
The base now includes main's adaptation to the upgraded engine (#40681), so the hold is over. oven-sh/WebKit#474 is re-applied on the current #472 head ( |
e6fa177 to
768ee79
Compare
63fb669 to
6517673
Compare
|
Pushed. oven-sh/WebKit#474 sits on the current #472 head (identical diff), its preview |
…er imports of it Extends the in-flight removal fixture so that a test can choose what the removed load does, and adds five shapes where it fails: the module throws while it evaluates, a dependency of it fails to load, a replacement load finished before the removed one failed, the module's own fetch is rejected, and require() of a top-level-await module whose first run rejects after a replacement import() succeeded. In each case the next import() of the path has to load the file again, or return the replacement. Before the engine change it rejected with the removed load's error, or the replacement import() never settled.
…e next import() Pins the part of the engine change that is visible without removing anything from require.cache: a rejected fetch no longer leaves a failed entry behind, and a failed entry left by a dependency is dropped by the next import() of it. A file with a syntax error loads once it is fixed on disk, by import() and by require(), also when a module that imports it failed first, and a plugin onLoad that rejected is called again by the next import(). Before, every later load replayed the first error.
…472 preview at ceb9f90f)
6517673 to
54f5bd2
Compare
|
#39674 moved once more while the previous push was being verified (it now pins the #472 preview |
|
Bot review of |
Stacked on #39674: its branch is the base, the same way oven-sh/WebKit#474 is stacked on oven-sh/WebKit#472. The diff is the pin moving from the #472 preview to the #474 preview, plus the tests. GitHub retargets this to
mainwhen #39674 lands.Problem
delete require.cache[path],mock.module()and pluginmodule()remove a module's registry entry, possibly while a load of it is in flight. If that load then failed, the loader stored the error under the path again: the nextimport()orrequire()rejected with the removed load's error and never loaded the file again. If a replacement load had registered the path first, the error landed in the replacement and a module that had loaded fine became unimportable, or the replacementimport()never settled. Module loader: removing a registry entry out from under the import cache no longer crashes the next import() (WebKit bump for oven-sh/WebKit#472) #39674 covers the crash flavor of the same window.moduleLoadTopSettled,moduleLoadTopRejectedandmoduleLoadStoreError(JSMicrotask.cpp) stored the error throughensureRegistered(key), which creates a fresh entry onceremoveEntry()(src/jsc/bindings/ZigGlobalObject.cpp:792,BunPlugin.cpp:706) has dropped the one the load started from.JSModuleLoader::loadModule()answers every later load of the key from that entry's error.Fix
mainsince the 8c4fd56347 upgrade, Upgrade WebKit to 8c4fd56347 #40276) no longer registers a rejected fetch and only stores an error into an entry that exists, so the key is no longer registered again. JSModuleLoader: a top-level load stores its failure into the entry it loaded WebKit#474 adds the fork residual: a top-level load records the entry it loaded and stores its failure into that entry, so a removed load cannot store into a replacement's entry. No Bun source change.onLoadthat rejected) is loaded again by the nextimport()orrequire(), so a file fixed in the meantime loads. Before, every later load replayed the first error for the life of the process.mainships that already but has no test for it; the tests here pin it.test/cli/run/require-cache.test.ts, three reload shapes intest/js/bun/resolve/build-error.test.tsandtest/js/bun/plugin/plugins.test.ts. On this PR's base (the Unable to set up devcontainer #472 preview, which has the upstream change) seven pass anda replacement load finished firstfails; all eight pass on the Bug: URL path join is not correct #474 preview. All eight failed on the pin before the upgrade. Module loader: removing a registry entry out from under the import cache no longer crashes the next import() (WebKit bump for oven-sh/WebKit#472) #39674's tests pass on the new pin too. Notes below.Background
ModuleRegistryEntry: the load's promises, its record, and one error.removeEntry()is a Bun addition to the engine. Upstream never removes entries, so for upstream "the entry for this key" and "the entry this load started from" are the same thing.ModuleLoadingContext. The per-dependency loads always held their entry in it. The top-level ones held only the key and looked the entry up again when the load failed.Notes
Fail-before: on the pin before the upgrade (the first #472 preview, and Bun 1.4.0 with the repro scripts) the four dynamic removal shapes print the removed load's error for the second import with
onLoadCalls: 1, therequire()shape hangs on the replacementimport()until the test times out, the twobuild-errortests getBuildMessagefor the fixed file, and the plugin test gets the first error back without a secondonLoadcall. On the current base (bun bd --webkit-version=autobuild-preview-pr-472-cfccea9d), which carries upstream 319474@main, onlya replacement load finished firstfails:getRegisteredMayBeNull()finds the replacement's entry and the removed load's error lands there. That is the shape oven-sh/WebKit#474 fixes. The engine-side split was first measured by recompiling the three affected unified-source bundles into the first #472 preview library, with and without the upstream change; the pass-after has been repeated on every published #474 preview since.Suites run on the new pin:
test/js/bun/resolve/,test/js/bun/plugin/,test/js/bun/test/mock/,test/js/node/module/,test/cli/run/,test/cli/test/, and the test of #33149. The failures left on this debug-asan container also fail on the unmodified engine: the sevenrequire.cacheleak tests (#39473 has the numbers),load the same empty JS file 2000 times(its sibling takes 13.6 s on both engines),--parallel lazily scales workers based on file duration,should resolve self-imports by name(a 5 s timeout around six debug-build spawns), the FUSE tests and theNO_ORPHANSuid/gid test.plugins.test.tsdoes not survive--rerun-eachon the base either (itsrecursionplugin overflows on re-registration), so the new test there was checked by a normal run; it uses a path of its own per run regardless.Open PRs this touches. #33419 works around the cached plugin failure on the Bun side; the upstream policy now on
maindoes what itsimport()cases ask for (the newplugins.test.tstest is that shape). #39204 (oven-sh/WebKit#451) is about which error object a later load of a failed file gets: for a later top-levelimport()orrequire()the answer is now a fresh load, while the per-dependency replay it also covers is unchanged. #33149 adds a test for oven-sh/WebKit#262; it passes here, but its comment describes a mechanism that is gone (nothing is registered for the rejected fetch any more, so the static importer fetches again). #38072 and #38645 are aboutrequire()of a module that is evicted during, or threw during, its own evaluation; they are different bugs and are not affected.If oven-sh/WebKit#472 and #474 merge together, the reverse packaging works too: this PR moves to the merged sha and #39674 becomes tests only.
Note
WEBKIT_VERSIONpoints at the preview tagautobuild-preview-pr-474-56b8426bwhile oven-sh/WebKit#474 is open. It has to move to the merged main sha before this lands, after #39674's pin does the same.no test proof · iteration 9 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/cli/run/require-cache.test.ts