Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 24 additions & 22 deletions test/regression/issue/32492.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// https://github.com/oven-sh/bun/issues/32492
import { expect, test } from "bun:test";
import { bunEnv, bunExe, tempDir } from "harness";
import { bunEnv, bunExe, isASAN, isDebug, tempDir } from "harness";

test("concurrent bun build does not stall on worker-pool shutdown", async () => {
const N_MODULES = 40;
Expand All @@ -24,41 +24,43 @@ test("concurrent bun build does not stall on worker-pool shutdown", async () =>
const root = String(dir);

const CONCURRENCY = 24;
const ROUNDS = 16;
// The regression is a fixed 10s idle-futex timeout, so a stalled build always
// exceeds 10s regardless of machine speed. A healthy build is well under a
// second; keep the threshold high so 24-way oversubscription on a slow ASAN
// shard can't trip it, while staying comfortably below the 10s floor.
// #32492 was a ~10s stall at pool shutdown when the idle-futex first-wait
// timeout was 10s. #34009 lowered that timeout to 100ms, after which the
// 16-round version of this test no longer distinguishes a reverted
// Event::wake fix on current main (verified empirically). This test is now a
// coarse guard against multi-second shutdown stalls plus a concurrent-build
// smoke test; 4 rounds x 24 builds covers that on debug/ASAN and release
// keeps 16 because it's cheap.
const ROUNDS = isASAN || isDebug ? 4 : 16;
const STALL_MS = 9000;

const buildOnce = async (round: number, i: number) => {
const buildOnce = async (i: number) => {
const entry = entries[i % entries.length];
const started = Date.now();
// Bundle to stdout (no --outdir) so we can assert on the bundle contents
// and skip per-build output directories. The regression is in thread pool
// shutdown, which runs identically regardless of the output sink.
await using proc = Bun.spawn({
cmd: [
bunExe(),
"build",
"--target=browser",
"--sourcemap=external",
"--packages=external",
"--outdir",
`${root}/out/d${round}_${i}`,
`./src/${entry}-entry.ts`,
],
cmd: [bunExe(), "build", "--target=browser", "--packages=external", `./src/${entry}-entry.ts`],
env: bunEnv,
cwd: root,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
return { ms: Date.now() - started, exitCode, stdout, stderr };
return { entry, ms: Date.now() - started, exitCode, stdout, stderr };
};

for (let round = 0; round < ROUNDS; round++) {
const results = await Promise.all(Array.from({ length: CONCURRENCY }, (_, i) => buildOnce(round, i)));
const failed = results.find(r => r.exitCode !== 0);
if (failed) {
throw new Error(`bun build exited with ${failed.exitCode}\nstdout:\n${failed.stdout}\nstderr:\n${failed.stderr}`);
const results = await Promise.all(Array.from({ length: CONCURRENCY }, (_, i) => buildOnce(i)));
for (const r of results) {
// Assert the bundle actually walked the 40-module chain before checking
// the exit code so a failure surfaces the build output.
expect(r.stderr).toBe("");
expect(r.stdout).toContain(`var v${N_MODULES} = ${N_MODULES};`);
expect(r.stdout).toContain("var v1 = 1 + v2;");
expect(r.stdout).toContain(`console.log("${r.entry}", v1, f1());`);
expect(r.exitCode).toBe(0);
}
const slowestMs = Math.max(...results.map(r => r.ms));
expect(slowestMs).toBeLessThan(STALL_MS);
Expand Down
Loading