Skip to content

chore(wb): drop --no-isolate from wb test once a stable Bun fixes the spawnSync hang #1425

Open

Description

@exKAZUu

Background and goal

wb test runs unit tests with bun test --parallel --no-isolate (#1421). --no-isolate works around a Bun 1.4.2 bug (oven-sh/bun#34069; unmerged fix oven-sh/bun#40078). With the default per-file isolation that --parallel implies, the stdout/stderr writers of a discarded per-file global can be garbage-collected inside a later spawnSync. That spawnSync and later ones in the same worker then spin without noticing the child's exit, the child stays a zombie, and the test hangs until its timeout (killed 1 dangling process). In CI this made normally sub-second tests (for example deployRailway.test.ts) time out in 3 of 7 Test runs on main.

--no-isolate makes the files of one worker share a global, a module registry, and process.env. Per-file isolation is the safer contract for every repository using wb, so it should be restored once the Bun bug is fixed in a stable release.

Behavior

Once a stable Bun release contains the fix from oven-sh/bun#40078 (or another fix that oven-sh/bun#34069 records as resolving it) and mise.toml pins it, testUnit in packages/wb/src/scripts/execution/baseScripts.ts passes --parallel without --no-isolate, and the comment there that references the Bun issue is removed.

Acceptance criteria

Scope and non-goals

In scope: the wb test flag, its comment, and the matching rule text in docs/expected-repository-rules.md. Not in scope: the environment-restoring test changes and the once-per-run buildWb build from #1421. They stay correct under isolation, so keep them.

Verification

Reproduction (Linux, which is what CI uses):

  1. Run Docker with 4 CPUs on the candidate Bun version (for example the oven/bun:<version> image or Ubuntu 24.04 with Bun installed by mise).

  2. Create 12 identical test files test/s1.test.mjs … test/s12.test.mjs with the following content:

    import { execFileSync } from 'node:child_process';
    import { test } from 'bun:test';
    
    const BYTES = 200000;
    const child = (i) => execFileSync('/bin/sh', ['-c', `yes abcdefghij | head -c ${BYTES}; echo e${i} >&2`], { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
    
    await Promise.resolve();
    for (let i = 0; i < 150; i += 1) {
      if (i % 4 === 0) await new Promise((r) => setTimeout(r, 0));
      else await Promise.resolve();
      if (child(i).length < BYTES) throw new Error('short read');
      if (i % 50 === 0) process.stdout.write(`round ${i}\n`);
    }
    test('loaded', () => {});
  3. Run each mode 10 times with stdout on a pipe and a 60 s cap, counting exits with code 124 as wedged:
    timeout 60 bun test ./test/ --parallel=4 | cat and timeout 60 bun test ./test/ --parallel=4 --no-isolate | cat.

Results on Bun 1.4.2 (aarch64 container, 4 CPUs):

  • --parallel=4: 7/10 and 9/10 runs wedged. A wedged worker stays in state R with its sh child a zombie.
  • --parallel=4 --no-isolate: 0/10 wedged.
  • --parallel=4 --preload of a file calling Bun.gc(true): 10/10 wedged, so that is not an alternative.

A fixed Bun must show 0/10 without --no-isolate; this is necessary but not sufficient (see Acceptance criteria). The same reproduction is published at https://github.com/exKAZUu/bun-spawnsync-hang-repro with per-build results in oven-sh/bun#34069 (comment).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions