Repository navigation
Conversation
Debug/ASAN child startup dominated wall time (100 sequential spawns of bun-debug at ~140ms each). Run in batches of 8 following the pattern in spawn-streaming-stdout.test.ts, scale to 40 iterations on debug/ASAN, and drop the per-iteration sleep(1) and console.log noise. Assertions now check stderr is empty, stdout matches Bun.version, exit code is 0, and the max fd is unchanged across the run. Previously the only assertion was that stdout was non-empty.
WalkthroughChangesSpawn stress validation
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
|
Status: diff is green; CI red is unrelated infra/flake.
Verified locally with |
There was a problem hiding this comment.
LGTM — test-only speedup that follows the established batched-spawn pattern from sibling tests.
What was reviewed:
- Batching loop bounds handle the non-multiple case (100 % 8 = 4) correctly via the
i + j < iterationsguard. --versionprintsGlobal::package_json_version(src/runtime/cli/mod.rs:2286) which is the same constantBun.versionexposes (src/bun_core/Global.rs:429), sostdout.trim() === Bun.versionholds across debug/release.getMaxFD()baseline is captured after the first batch'sawait usingscopes have exited — same fd-leak-guard shape asspawn-streaming-stdout.test.ts:42-47.- Pipes drained concurrently via
Promise.all,bunEnvpassed so debug stderr stays empty.
Extended reasoning...
Overview
This PR rewrites test/js/bun/spawn/spawn-stress.test.ts — a single test file, no production code. The old test ran 100 sequential bunExe() --version spawns with a 1ms sleep between each, per-iteration console.log, a 99999999 timeout, and only asserted stdout != "". The new version batches 8 concurrent spawns per iteration (40 total on debug/ASAN, 100 on release), drops the sleep/logging/timeout, and asserts exact stdout/stderr/exitCode plus an fd-leak check.
Security risks
None. Test-only change; spawns the test's own bunExe() with --version, no network, no filesystem writes, no untrusted input.
Level of scrutiny
Low. This is a test-infrastructure speedup with no runtime code touched. The batched-concurrency + getMaxFD() shape is copied nearly verbatim from spawn-streaming-stdout.test.ts and spawn-streaming-stdin.test.ts, so the pattern is already proven in CI across platforms. The main things worth checking were (a) whether the tightened assertions could introduce flakiness and (b) whether the loop bounds are correct — both check out.
Other factors
- Verified
--versionandBun.versionshare the same source constant (package_json_version), including the-debugsuffix on debug builds, so the exact-match assertion won't diverge. bunEnvis now passed, which setsBUN_DEBUG_QUIET_LOGS=1— necessary for the newstderr === ""assertion to hold on debug builds.await using proc+Promise.all([stdout.text(), stderr.text(), proc.exited])matches the harness conventions in test/CLAUDE.md and REVIEW.md (drain pipes concurrently, RAII cleanup).- The fd baseline is captured after the first batch fully resolves (all
await usingdisposals have run), so the finalexpect(getMaxFD()).toBe(maxFD)compares steady-state to steady-state — identical to the sibling test. - Author reports 14.71s → 3.15s under debug+ASAN with 5 consecutive clean runs; the change removes a
sleeprather than adding one, which is the right direction for flakiness.
Flaked on alpine 3.23 aarch64 (build #85082): pipe-backing fds close on the event-loop tick after the child exits, so a getMaxFD() immediately after the final batch can observe fds that reclaim one tick later. spawn-streaming-stdout and spawn-streaming-stdin already cover the fd-leak path with the right wait machinery (expectMaxObjectTypeCount GC loop), so keep this test focused on pipe-output correctness.
There was a problem hiding this comment.
LGTM — test-only speedup that follows the sibling spawn-streaming test pattern.
What was reviewed:
- Batching/iteration-scaling matches
spawn-streaming-stdout.test.ts;bunEnv/await using/concurrent pipe drain follow harness conventions. - Assertions tightened (exact
Bun.version,stderr === "",exitCode === 0) replace the vacuous!= ""check;bunEnvsilences debug logs so the stderr assertion holds. - Follow-up commit b2d8507 drops the
getMaxFDguard after an alpine-aarch64 flake — rationale is sound (pipe fds close a tick late; fd-leak coverage lives in the streaming siblings). The PR description still mentions it, but the net diff is correct. - Dropped
99999999timeout andBun.sleep(1)are both explicit anti-patterns per test guidelines.
Extended reasoning...
Overview
Single-file test refactor of test/js/bun/spawn/spawn-stress.test.ts. The PR contains two commits: 2e5b5ef rewrites the test to batch spawns in groups of 8 (scaled to 40 iterations under debug/ASAN, 100 on release), passes bunEnv, drains stdout/stderr concurrently via Promise.all, uses await using for cleanup, drops the 99999999 timeout and per-iteration Bun.sleep(1)/console spam, and strengthens assertions from out != "" to exact matches on stderr === "", stdout.trim() === Bun.version, and exitCode === 0. b2d8507 then removes a getMaxFD() fd-leak guard that flaked on alpine 3.23 aarch64 (pipe-backing fds close on the tick after child exit), deferring that coverage to spawn-streaming-stdout/stdin which already have the correct GC-loop wait machinery.
Security risks
None. Test-only change; no production code, no new inputs, no network, no filesystem writes beyond what Bun.spawn already does.
Level of scrutiny
Low. This is a mechanical test-speedup that copies an established pattern verbatim from spawn-streaming-stdout.test.ts (concurrency-batched loop, same structure). Every change maps directly onto an explicit repo convention: bunEnv when spawning, concurrent pipe draining to avoid the 64KB deadlock, await using for cleanup, no test timeouts, no sleep, isASAN/isDebug for workload scaling, exact toBe over weak not.toBe("").
Other factors
- Verified the batching pattern against
spawn-streaming-stdout.test.ts:8-47— same shape (outer loop stepping by concurrency, inner loop pushing async IIFEs,Promise.allper batch). - The
Bun.versioncomparison is safe:--versionandBun.versionboth surfaceGlobal::package_json_version, andbunExe()guarantees the child is the same binary as the parent. - The
stderr === ""assertion is safe becausebunEnvsetsBUN_DEBUG_QUIET_LOGS=1, so debug builds won't emit noise. - Reducing debug/ASAN iterations 100→40 is a deliberate workload shrink (per REVIEW.md: shrink workload rather than raise timeout); the failure mode being guarded — spawn path crashing or output going missing over many iterations — is still exercised at 40 iterations × 8-way concurrency, and release CI keeps 100.
- The dropped
getMaxFDcheck in the second commit has a clear root-cause explanation and names where the coverage remains, satisfying the "never silently weaken" rule. - Author reports 14.71s → 3.15s under debug+ASAN with 5 consecutive clean runs; net expect() count went up (100 → 121).
The child binary is incidental (the test originally spawned clang). Using sh -c 'echo ok' (cmd.exe on Windows) removes the 140ms-per-spawn debug+ASAN bun startup cost, so the full 100 iterations run everywhere instead of scaling to 40 on debug/ASAN. Same portable-child pattern as spawn-many-teardown.test.ts. Verified on linux-x64 debug+ASAN (100 iters, 0.72s) and windows-x64 release (100 iters, 0.14s).
There was a problem hiding this comment.
LGTM — test-only speedup that strengthens assertions rather than weakening them.
What was reviewed:
- Confirmed the
process.env.comspec || "cmd.exe"//bin/shchild pattern matchesspawn-many-teardown.test.ts:17-18(already green in CI on all platforms). - Confirmed the batch-of-N loop matches
spawn-streaming-stdout.test.ts(concurrency 7 there, 8 here). - Checked that the original invariant (output not lost / no crash across 100 spawns) is still asserted, now more strictly: exact stdout, empty stderr, exit 0 — previously only
stdout != "". - Verified
.trim()handles both\nand\r\nso the Windowscmd.exe /c echo okoutput passes the same assertion.
Extended reasoning...
Overview
Single-file test refactor of test/js/bun/spawn/spawn-stress.test.ts. Replaces 100 sequential bunExe() --version spawns (each a full debug+ASAN Bun startup) with 100 batched sh -c "echo ok" / cmd.exe /c echo ok spawns, drops the per-iteration sleep(1) and 200 lines of console spam, switches to stream.text() + await using + concurrent pipe drain, and removes the 99999999 timeout. No runtime code is touched.
Security risks
None. Test-only; spawns a fixed shell command with no user input.
Level of scrutiny
Low. This is a test-speed PR that follows the REVIEW.md guidance directly: it removes a sleep, drains pipes with Promise.all, uses await using, passes bunEnv, and — critically — does not weaken the property being tested. The new assertions (stderr === "", stdout.trim() === "ok", exitCode === 0) are strictly stronger than the old out != "". The iteration count stays at 100 on every lane instead of being scaled down.
Other factors
- The cheap-child and batching patterns are copied verbatim from sibling tests (
spawn-many-teardown.test.ts,spawn-streaming-stdout.test.ts) that already run on all CI lanes including Windows, so theprocess.env.comspec/cmd.exepath is proven. - The PR description notes the test originally spawned
clang --version; the child binary was always incidental, so swapping it forsh/cmd.exedoesn't change what's under test (Bun's spawn path, not the child). - The one semantic shift — 8-way concurrent batches vs strictly sequential — matches how the other spawn stress tests in this directory are structured, and if anything stresses the spawn path harder.
- Verified locally per PR description on debug+ASAN and Windows release; waiting on CI, but nothing here is lane-specific beyond patterns already in use.
|
Updated 10:05 AM PT - Jul 29th, 2026
❌ @robobun, your commit 65b7f63 has some failures in 🧪 To try this PR locally: bunx bun-pr 36345That installs a local version of the PR into your bun-36345 --bun |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@test/js/bun/spawn/spawn-stress.test.ts`:
- Line 32: Strengthen the stdout assertion in the spawn stress test by removing
only the optional platform newline before comparing with "ok"; do not use
trim(), so unexpected leading or trailing whitespace causes the assertion to
fail.
- Around line 5-10: Update the comment above the stress test to remove the
historical reference to the previous child command and the incidental binary.
Keep only the durable rationale: repeatedly drain both pipes and overlap spawns
in small, bounded batches.
- Line 13: Update the Windows command array in the spawn stress test to include
the /d switch with cmd.exe before /c, while preserving the existing non-Windows
command and echo behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 850a968e-1fcd-420a-bd34-6a0dc1f8461a
📒 Files selected for processing (1)
test/js/bun/spawn/spawn-stress.test.ts
There was a problem hiding this comment.
LGTM — test-only speedup that follows existing spawn-test patterns and strictly tightens assertions.
What was reviewed:
- Batching (concurrency=8) and
await using/concurrent pipe-drain matchspawn-streaming-stdout.test.ts; thesh/cmd.exechild matchesspawn-many-teardown.test.ts(including theprocess.env.comspecfallback). - Confirmed the dropped
getMaxFDfd-leak check (commit b2d8507) is redundant with the GC-looped fd-leak coverage inspawn-streaming-stdout/stdin. - Assertions are strictly stronger than before (exact stdout, empty stderr, exit 0 — vs. only
stdout != "");.trim()handles Windows CRLF.
Extended reasoning...
Overview
Single-file, test-only change to test/js/bun/spawn/spawn-stress.test.ts. The test previously spawned bunExe() --version 100× sequentially with a 1ms sleep and 200 lines of console noise per run, taking ~14.7s under debug+ASAN. It now spawns sh -c "echo ok" (or cmd.exe /c echo ok on Windows) in batches of 8, drains both pipes concurrently via Promise.all, uses await using for cleanup, and asserts exact stdout/stderr/exitCode. The huge 99999999 timeout override is removed. An intermediate commit added and then removed a getMaxFD fd-leak check after it flaked on alpine aarch64 — the commit message correctly notes fd-leak coverage already exists in spawn-streaming-stdout/stdin with proper GC-wait machinery.
Security risks
None. Test-only; spawns /bin/sh / cmd.exe with a fixed literal argument list, no user-controlled input, no network.
Level of scrutiny
Low. This is a CI-speed refactor of an existing stress test with no runtime code changes. The two questions that matter are (1) does it still exercise the original failure mode and (2) does it follow harness conventions. Both check out: the comment documents the original failure mode (output going missing / spawn-path crash after many iterations), which is preserved — 100 iterations still run, both pipes are still drained, and concurrent batching is at least as stressful as sequential. The child binary was always incidental (originally clang --version). Harness conventions are correctly followed: bunEnv, await using, concurrent Promise.all([stdout, stderr, exited]) per REVIEW.md's pipe-drain rule, no timeout override, no sleep.
Other factors
- Every claimed pattern reference is real: batching mirrors
spawn-streaming-stdout.test.ts:8-45; the portable child mirrorsspawn-many-teardown.test.ts:16-18. - Assertions are strictly tighter than before (
toBe("ok")/toBe("")/toBe(0)vs. the oldnot.toBe("")), so no coverage is weakened. .trim()on stdout handles the Windows\r\nfromcmd.exe; author reports Windows x64 release verified at ~142ms.process.env.comspeccase-insensitivity on Windows matches the existing usage inspawn-many-teardown.test.ts, which is already green in CI.- The dropped fd-leak check has a clear rationale in b2d8507 (pipe fds close one tick after exit; the sibling tests that do assert this use
expectMaxObjectTypeCountwith a GC loop). - No outstanding human review comments; only bot noise on the timeline.
|
Closing this PR because it has been inactive for more than 90 days. |
What does this PR do?
spawn-stress.test.tswas taking ~9s on debian 13 x64-asan (build #84540) and ~14.7s locally under debug+ASAN. The test body is 100 sequential spawns ofbunExe() --version; debug+ASAN child startup (~140ms each) dominates wall time, notBun.spawnitself.Changes:
sh -c "echo ok"(cmd.exe /c echo okon Windows) instead ofbunExe() --version. The child binary is incidental here (the test was originally written withclang --version;bunExe()was swapped in for CI availability). Same portable-child pattern asspawn-many-teardown.test.ts. This keeps 100 iterations on every lane instead of scaling down for debug/ASAN.spawn-streaming-stdout.test.ts/spawn-streaming-stdin.test.ts).await Bun.sleep(1)andconsole.logspam (200 lines per run).new Response(stream).text()withstream.text()and useawait usingfor process cleanup.99999999per-test timeout.Assertion strengthening:
stderr === ""(previously ignored).stdout.trim() === "ok"(previously only!= "").exitCode === 0(previously unchecked).How did you verify your code works?
bun bd test test/js/bun/spawn/spawn-stress.test.tslocally under debug+ASAN:Release (
USE_SYSTEM_BUN=1): ~150ms test body. Windows x64 release: ~142ms test body. Ran repeatedly under all three with no variance.PR #33704 also touches this file (iteration count only) as part of a 53-file omnibus; this change supersedes that hunk.
no test proof · iteration 1 · Platform-specific test-only change; deferring to CI.