diff --git a/scripts/build/deps/webkit.ts b/scripts/build/deps/webkit.ts index 3a03ee0ca8c..c940da88ecf 100644 --- a/scripts/build/deps/webkit.ts +++ b/scripts/build/deps/webkit.ts @@ -3,7 +3,7 @@ * for local mode. Override via `--webkit-version=` to test a branch. * From https://github.com/oven-sh/WebKit releases. */ -export const WEBKIT_VERSION = "88b2f7a2159c913f7dd0d73c0e88d66138cd67ba"; +export const WEBKIT_VERSION = "5488984d20e0dbfe4be2c3ba8fb18eb81a5e0e8b"; /** * WebKit (JavaScriptCore) — the JS engine. diff --git a/test/regression/issue/30493.test.ts b/test/regression/issue/30493.test.ts new file mode 100644 index 00000000000..7b358066611 --- /dev/null +++ b/test/regression/issue/30493.test.ts @@ -0,0 +1,51 @@ +// https://github.com/oven-sh/bun/issues/30493 +// +// `require()` of an ESM module whose graph contains a diamond dependency +// through a barrel deadlocked (release) / aborted on `ASSERTION FAILED: +// m_status == Status::Fetching` at ModuleRegistryEntry.cpp:254 (debug). +// Regressed by the require(esm) sync-replay path; same root cause as +// #30281 — `moduleRegistryModuleSettled` fired twice for the same entry +// when `hostLoadImportedModule`'s synchronous-replay branch had already +// driven `fetchComplete` inline while a stale normal-queue reaction was +// still pending. Fix: oven-sh/WebKit#225. +// +// This is the dependency-free reduction of #30281; it subsumes the +// react + MUI repro from #30283. + +import { expect, test } from "bun:test"; +import { bunEnv, bunExe, normalizeBunSnapshot, tempDir } from "harness"; + +test("require() of ESM with diamond dependency through barrel does not deadlock", async () => { + using dir = tempDir("issue-30493", { + // shared.js: imports a synthetic builtin so its fetch goes through + // the normal microtask queue *before* the require(esm) entry point + // switches to synchronous draining — that ordering is what leaves a + // stale ModuleRegistryModuleSettled reaction queued. `path.posix.sep` + // so the snapshot is platform-stable. + "shared.js": `import path from 'path';\nexport const SHARED = path.posix.sep;\n`, + "barrel.js": `import { SHARED } from './shared.js';\nexport { SHARED };\nexport const BARREL = 'barrel';\n`, + "a.js": `import { SHARED } from './barrel.js';\nexport default function a() { return SHARED; }\n`, + "b.js": `import { BARREL } from './barrel.js';\nexport default function b() { return BARREL; }\n`, + "app.js": `import a from './a.js';\nimport b from './b.js';\nimport { SHARED } from './shared.js';\nexport default { a: a(), b: b(), shared: SHARED };\n`, + "entry.js": `const mod = require('./app.js');\nconsole.log(JSON.stringify(mod.default));\n`, + }); + + await using proc = Bun.spawn({ + cmd: [bunExe(), "entry.js"], + cwd: String(dir), + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + // Before the fix: release builds deadlock indefinitely; debug builds + // abort. Bound the subprocess so the assertions below show a clear + // failure instead of the test itself timing out. + timeout: 10_000, + }); + + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + expect(normalizeBunSnapshot(stdout, dir)).toMatchInlineSnapshot(`"{"a":"/","b":"barrel","shared":"/"}"`); + // null ⇒ exited on its own; non-null ⇒ killed by the spawn timeout (deadlocked). + expect(proc.signalCode).toBeNull(); + expect(exitCode).toBe(0); +}, 30_000);