52 lines
2.7 KiB
TypeScript
52 lines
2.7 KiB
TypeScript
// 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);
|