Skip to content

ci: fix server.allocator vtable mismatch + 3 broken-on-release tests - #29926

Merged
Jarred-Sumner merged 7 commits into
mainfrom
claude/ci-fixes-allocator-asan-windows
Apr 29, 2026
Merged

ci: fix server.allocator vtable mismatch + 3 broken-on-release tests#29926
Jarred-Sumner merged 7 commits into
mainfrom
claude/ci-fixes-allocator-asan-windows

Conversation

@Jarred-Sumner

Copy link
Copy Markdown
Collaborator

Clears four pre-existing failures showing up across open PRs.

server.allocatorbun.default_allocator

MimallocArena.getThreadLocalDefault() and bun.default_allocator both call mimalloc, but their vtables differ. Collections that flow between server-owned and default-owned code (e.g. BabyList in the response sink, the onUpgrade path) trip CheckedAllocator.assertEq in ci_assert builds:

allocator mismatch: cannot use multiple allocators with the same collection
panic(main thread): Internal assertion failure: allocators do not match

This was crashing both serve-stream-reject-flush-leak.test.ts and the websocket upgrade-UAF test on Windows release lanes. Same change as #29916 minus the X509/BIO hunks that already landed via #29881.

html-rewriter-leak.test.ts — RSS path never worked on release

The test was added in #29879 and failed on every release platform in that PR's own CI (113–233 MB across darwin/linux/windows/asan, all on the fixed build). Only the debug path (precise mimalloc counters) passed.

Root cause: a single trailing Bun.gc(true) does collect all 16k rewriters, but neither mimalloc nor ASAN's allocator promptly return freed pages to the OS, so RSS pins at the peak live set rather than what's retained. Now GCs every 1k iterations to bound peak ≈ retained.

build before after
release post-fix 148 MB ❌ 0.0 MB ✅
asan post-fix 233 MB ❌ 0.6 MB ✅
release pre-fix 68.5 MB ❌ (still detects the leak)
debug post-fix

serve-stream-reject-flush-leak.test.ts — skip on Windows

Once the allocator panic is fixed, the test next fails on Windows with insufficient backpressure: 0/40 (#29916 CI) — the 8 MiB tryEnd() fits in Windows' auto-tuned loopback send buffer so pending_flush is never created. The leak is platform-agnostic Zig; POSIX coverage is sufficient.

websocket-server.test.ts — un-truncate stderr

The upgrade-UAF test's stderr.split("\\n", 3) hid the panic line, leaving {stdout:"", stderr:""} and a misleading diff.

test-integration-rspack.ts — pin to rsbuild@1

create-rsbuild@2.0.0 (Apr 22) pulls @rspack/binding-win32-arm64-msvc@2.0.x which bundles mimalloc v3. Two static mimalloc instances → deterministic segfault in ntdll during ExitProcess on Windows arm64 (crash addr ends in b9c8 across 5 builds). The test exists to guard the napi TSFN finalizer, not rsbuild HEAD.

Supersedes #29916.

server: use bun.default_allocator instead of MimallocArena.getThreadLocalDefault().
Both route to mimalloc, but the vtables differ, so collections that flow
between server-owned and default-owned code (BabyList in the response sink,
upgrade path) trip CheckedAllocator in ci_assert builds. Fixes the
"allocators do not match" panic in serve-stream-reject-flush-leak and the
silent-crash in the websocket upgrade UAF test on Windows release lanes.
(Same change as #29916, without the unrelated X509 hunks already merged via #29881.)

test(serve-stream-reject-flush-leak): skip on Windows. The fixture needs an
8 MiB tryEnd() to hit loopback backpressure; Windows auto-tunes the send
buffer past that so pending_flush is never created (0/40 on #29916 CI).

test(html-rewriter-leak): GC every 1k iterations instead of once at the end.
The RSS path never worked on any release platform (#29879's own CI: 113-233 MB
across darwin/linux/windows/asan, all post-fix). A single trailing Bun.gc(true)
collects everything but neither mimalloc nor ASAN promptly return freed pages
to the OS, so RSS pins at the 16k-builder peak. Batching bounds peak ≈ retained.
Verified: pre-fix release fails at 68 MB; post-fix release/asan/debug pass at <1 MB.

test(websocket-server): stop truncating stderr to 3 lines in the upgrade UAF
test — the panic line was past line 3, leaving an empty diff.

test(integration-rspack): pin create-rsbuild to @1. rsbuild 2.0.x bundles
mimalloc v3 inside @rspack/binding-win32-arm64-msvc; two static mimalloc
instances segfault deterministically in ntdll during ExitProcess on Windows arm64.
@robobun

robobun commented Apr 29, 2026

Copy link
Copy Markdown
Collaborator
Updated 6:15 AM PT - Apr 29th, 2026

@Jarred-Sumner, your commit 943015a has 4 failures in Build #49045 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 29926

That installs a local version of the PR into your bun-29926 executable, so you can run:

bun-29926 --bun

@coderabbitai

coderabbitai Bot commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Multiple test files are updated to improve robustness and debugging: an Rspack integration test pins to a specific version with clarifying comments about a segfault scenario, a WebSocket protocol test removes stderr output truncation, a memory leak test switches from mimalloc stats to RSS-based measurements with conditional debug mode handling, and an HTTP streaming test fixture increases response body allocation with Windows-specific documentation.

Changes

Cohort / File(s) Summary
Integration Test Configuration
test/js/bun/test/parallel/test-integration-rspack.ts
Pins Rsbuild dependency to version 1 instead of latest, and adds inline comments describing the mimalloc/Windows arm64 ExitProcess segfault scenario the test guards against.
Protocol Regression Test
test/js/bun/websocket/websocket-server.test.ts
Removes stderr output truncation in the server.upgrade() Sec-WebSocket-Protocol test, comparing stderr.trim() directly to an empty string to prevent truncated diffs when panic output extends beyond the third line.
Memory Leak Detection
test/js/workerd/html-rewriter-leak.test.ts
Refactors leak detection from mimalloc stats to fixed RSS-based measurements with conditional debug mode skipping; removes debug/release-dependent warmup logic and replaces it with fixed pass() batching, explicit Bun.gc(true) calls, and high-water mark RSS tracking; forces BUN_GARBAGE_COLLECTOR_LEVEL to 0 in spawned environment.
HTTP Streaming Test Fixture
test/js/bun/http/serve-stream-reject-flush-leak-fixture.ts
Increases reusable response body chunk allocation from 8 MiB to 64 MiB with documentation on Windows loopback buffer behavior; reformats failure-path logging to a single-line console.error call.

Possibly related PRs

  • oven-sh/bun#29916: Modifies the same test fixture (serve-stream-reject-flush-leak-fixture.ts) with related changes to serve-stream-reject-flush test allocation and logging behavior.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: fixing a server.allocator vtable mismatch and addressing three broken release tests, matching the PR's primary objectives.
Description check ✅ Passed The description provides comprehensive detail on all four fixes with technical context, test results, and rationale. It exceeds the minimal template requirements with specific sections addressing each issue.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. server: use bun.default_allocator for server.allocator #29916 - Same server.allocator vtable mismatch fix (replacing MimallocArena.getThreadLocalDefault with bun.default_allocator in server.zig); this PR explicitly supersedes server: use bun.default_allocator for server.allocator #29916

🤖 Generated with Claude Code

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found. The four test changes are clean CI hygiene, but swapping server.allocator to bun.default_allocator in server.zig touches the HTTP server's allocator init — functionally it looks equivalent (both route to mimalloc, and getThreadLocalDefault already returns bun.default_allocator under ASAN), but it's a critical-path change worth a human glance.

Extended reasoning...

Overview

This PR bundles five changes aimed at clearing pre-existing CI failures:

  • src/bun.js/api/server.zig — replaces MimallocArena.getThreadLocalDefault() with bun.default_allocator for server.allocator, and drops the now-unused Arena import. Both allocators dispatch to mimalloc; the difference is vtable identity, which was tripping CheckedAllocator.assertEq in ci_assert builds.
  • test/js/workerd/html-rewriter-leak.test.ts — switches from one trailing GC to batched GC every 1k iterations so RSS tracks retained (not peak) memory on release/ASAN builds.
  • test/js/bun/http/serve-stream-reject-flush-leak.test.ts — wraps the test in test.skipIf(isWindows) because Windows loopback buffers absorb the 8 MiB write without backpressure.
  • test/js/bun/websocket/websocket-server.test.ts — stops truncating stderr to 3 lines so panic output is visible in failure diffs.
  • test/js/bun/test/parallel/test-integration-rspack.ts — pins create rsbuild@1 instead of @latest to avoid a Windows-arm64 double-mimalloc segfault from rsbuild 2.x.

Security risks

None identified. No auth, crypto, input parsing, or network surface changes. The allocator swap is between two vtables that both call into mimalloc; no new allocator or untrusted data path is introduced.

Level of scrutiny

Mixed. The four test-file edits are low-risk, mechanical CI stabilization (skip-on-platform, version pin, GC cadence, stderr formatting) and would be approvable on their own. The server.zig change, while small and well-justified, alters allocator initialization for every Bun.serve() instance — a hot, production-critical path. I verified in src/allocators/MimallocArena.zig:119-124 that getThreadLocalDefault() already documents "there is no per-thread default heap to cache" and returns bun.default_allocator under ASAN, so the runtime behavior should be identical. Still, allocator identity in the server is the kind of thing where a maintainer should confirm there are no other consumers relying on the old vtable.

Other factors

  • No CODEOWNERS coverage on server.zig.
  • Bug-hunting system found nothing.
  • PR description is detailed with before/after measurements and references to the superseded #29916.
  • No prior human reviews or outstanding comments on the thread.

The previous version's RSS path failed on every release platform from the PR
that added it (#29879's own CI: 113-233 MB). A single trailing Bun.gc(true)
collects everything, but mimalloc/ASAN don't promptly return pages to the OS,
so RSS measured peak live set, not retained.

Now: 3 identical warmup passes to establish the allocator high-water mark,
then 3 measured passes. Any RSS growth past the warmup plateau is what's
actually retained.

  release post-fix:  pass, ~-0.6 MB, 1.5s
  release pre-fix:   fail,  +52 MB, 1.2s
  asan post-fix:     pass,  ~0.0 MB, 7.7s

Threshold 25 sits at ~half the unfixed signal. Debug skipped: ~40s/pass and
debug-allocator tracking noise drowns the signal; CI has no debug test lane.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No bugs found and the test fixes look sound, but swapping server.allocator in Bun.serve() init is a core-runtime change — even though both vtables route to the same mimalloc global heap, I'd like a human to sign off on that hunk.

Extended reasoning...

Overview

This PR bundles one runtime change with four test-stability fixes:

  • src/bun.js/api/server.zig: replace MimallocArena.getThreadLocalDefault() with bun.default_allocator for the server's .allocator field, and drop the now-unused Arena import.
  • serve-stream-reject-flush-leak.test.ts: wrap in test.skipIf(isWindows) because Windows loopback auto-tunes the send buffer large enough that the 8 MiB write never backpressures, so the fixture's precondition can't be met.
  • test-integration-rspack.ts: pin create rsbuild@1 instead of @latest to avoid a known double-mimalloc segfault on Windows arm64 with rsbuild 2.0.x.
  • websocket-server.test.ts: stop truncating stderr to 3 lines so panic output isn't hidden.
  • html-rewriter-leak.test.ts: rewrite the RSS-based leak detection to use symmetric warmup/measure passes with per-pass GC, skip on debug, and pin BUN_GARBAGE_COLLECTOR_LEVEL=0.

Security risks

None. No auth, crypto, input parsing, or privilege boundaries are touched. The allocator swap is between two interfaces to the same underlying mimalloc heap; it does not change ownership or lifetime semantics.

Level of scrutiny

The four test-file changes are low-risk CI stabilization (platform skips, version pin, assertion-output tweak, leak-test methodology). The server.zig change, however, alters which allocator every Bun.serve() instance uses. I checked MimallocArena.getThreadLocalDefault() (src/allocators/MimallocArena.zig:121) and confirmed it already returns bun.default_allocator under ASAN and otherwise a vtable that routes to the same global mimalloc functions — so the change is a vtable-identity normalization, not a behavioral change. The ~6 uses of this.allocator in server.zig are plain alloc/free/dupe with no arena-lifecycle assumptions. Still, "change the allocator for the HTTP server" is the kind of core-path edit that benefits from a human glance rather than bot auto-approval.

Other factors

  • No CODEOWNERS entry covers these files.
  • The PR description is thorough, includes before/after measurements for the html-rewriter test across release/asan/pre-fix builds, and confirms the leak is still detected on a regressed build.
  • The html-rewriter test now skips debug builds entirely; the comment explains CI has no debug test lane, which is a reasonable trade-off but does drop the previously-passing precise-counter path.
  • Pinning rsbuild to a major version is a standard pattern for integration tests guarding a specific regression rather than upstream HEAD.

…nstead of skipping

Replace skipIf(isWindows) with the same fixture change pushed to #29916:
pause the client so Windows' loopback fast-path can't absorb the 8 MiB
write, then destroy the socket from the server side via setImmediate after
the throw (handleRejectStream runs on the microtask queue first).
Backpressure is now deterministic per-iteration (paused client), and the
metric is heapStats().protectedObjectTypeCounts.Promise (exact). 40 was
hedging for the old on('data', destroy) timing.
… loopback

socket.pause() only stops userspace reads — Windows' kernel SO_SNDBUF +
SO_RCVBUF autotune to ~16 MiB combined on loopback and still absorb the
full 8 MiB synchronously (0/10 backpressure on Win 11 aarch64). 64 MiB
outsizes that. The buffer is reused across iterations so this only costs
one larger allocation.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional findings (outside current diff — PR may have been updated during review):

  • 🟡 test/js/bun/http/serve-stream-reject-flush-leak-fixture.ts:67-78 — nit: the PR description still has a section titled "serve-stream-reject-flush-leak.test.ts — skip on Windows" stating "POSIX coverage is sufficient", but commit a721c57 changed the approach — the fixture now socket.pause()s and destroys from the server side to force backpressure on Windows too (no skipIf anywhere). Since the description becomes the squash-merge commit message, it's worth updating that section so future readers don't think Windows coverage was dropped.

    Extended reasoning...

    What's mismatched

    The PR description contains this section:

    serve-stream-reject-flush-leak.test.ts — skip on Windows

    Once the allocator panic is fixed, the test next fails on Windows with insufficient backpressure: 0/40 … the 8 MiB tryEnd() fits in Windows' auto-tuned loopback send buffer so pending_flush is never created. The leak is platform-agnostic Zig; POSIX coverage is sufficient.

    But the code that actually landed does not skip on Windows. Commit a721c57 (test(serve-stream-reject-flush-leak): force backpressure on Windows instead of skipping) replaced the skip with a deterministic-backpressure approach, and the in-code comment at lines 67–71 of the fixture documents it explicitly:

    Windows' loopback fast-path will absorb the full 8 MiB into the kernel if the client is draining, so explicitly pause(); the server side destroys the socket once handleRejectStream has run.

    Step-by-step verification

    1. grep -E 'isWindows|skipIf|process\.platform' test/js/bun/http/serve-stream-reject-flush-leak* → no matches. Neither the fixture nor serve-stream-reject-flush-leak.test.ts contains any platform guard.
    2. git log --oneline -- test/js/bun/http/serve-stream-reject-flush-leak-fixture.ts shows:
      • a721c57 test(serve-stream-reject-flush-leak): force backpressure on Windows instead of skipping
      • e1e0cba test(serve-stream-reject-flush-leak): drop ITERATIONS 40 → 10
        The author explicitly pivoted mid-PR from "skip" to "force backpressure".
    3. The fixture now (a) calls socket.pause() immediately after writing the request line so the kernel can't drain into userspace, and (b) schedules setImmediate(() => currentSocket?.destroy()) from inside pull() so the parked tryEnd() connection is torn down after handleRejectStream runs. This makes the backpressure path deterministic on all platforms, including Windows.

    Why it matters

    In this repo the PR description becomes the squash-merge commit message. A future reader running git log / git blame on this test will see prose claiming "POSIX coverage is sufficient" and may reasonably conclude the test has no Windows coverage — when in fact Windows coverage was deliberately kept and the fixture was hardened specifically for it. That's the opposite of what shipped.

    Fix

    Before merging, replace the "skip on Windows" section of the PR description with something like:

    serve-stream-reject-flush-leak.test.ts — force backpressure on Windows

    Once the allocator panic is fixed, the test next failed on Windows with insufficient backpressure: 0/40 because the 8 MiB tryEnd() fits in Windows' auto-tuned loopback send buffer. Fixed by having the client pause() immediately after sending the request and having the server destroy the socket via setImmediate after handleRejectStream runs, so pending_flush is created deterministically on all platforms.

    This is purely a documentation/metadata fix — no code change required.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — test-only CI stabilization; the stale 8 MiB comment is a non-blocking nit.

Extended reasoning...

Overview

This PR touches four test files only — no runtime/production code. The server.allocatorbun.default_allocator Zig change referenced in the title already landed on main via #29916 (commit 306b381), so the actual diff here is purely test stabilization:

  • test-integration-rspack.ts: pin rsbuild@latestrsbuild@1 to dodge a known mimalloc-v3 double-instance segfault on Windows arm64.
  • websocket-server.test.ts: stop truncating stderr to 3 lines so panic output is visible in failure diffs.
  • serve-stream-reject-flush-leak-fixture.ts: bump the reused body chunk 8 MiB → 64 MiB so Windows loopback can't absorb the whole write and pending_flush is actually exercised; plus a cosmetic console.error reflow.
  • html-rewriter-leak.test.ts: rewrite the leak-measurement methodology to use identical warmup/measure RSS passes (so allocator footprint plateaus before the baseline), force BUN_GARBAGE_COLLECTOR_LEVEL=0, skip on debug builds, and tighten the timeout to 15s.

Security risks

None. All changes are confined to test fixtures and assertions. No auth, crypto, network-input parsing, or production code paths are touched.

Level of scrutiny

Low-to-moderate. Three of the four changes are mechanical (version pin, un-truncate stderr, buffer-size bump). The html-rewriter-leak.test.ts rewrite is the only non-trivial piece, but it's a test-methodology change backed by an empirical table in the PR description showing the new approach passes on release/ASAN post-fix (0–0.6 MB) and still flags the regression pre-fix (68.5 MB vs the 25 MB threshold). The skipIf(isDebug) is justified since CI has no debug test lane and the comment documents why.

Other factors

The one inline finding is a stale "8 MiB" reference in the oneRequest() comment after the CHUNK bump — purely a documentation inconsistency with zero behavioral impact, not worth blocking on. The robobun build failures on commit 79eb6c4 are blanket build-zig/build-cpp infra failures across every platform from the main merge, unrelated to these test-only edits. Each change is well-commented with the underlying root cause, which makes the intent easy to verify.

Comment thread test/js/bun/http/serve-stream-reject-flush-leak-fixture.ts Outdated
… is unreachable there

Winsock's non-blocking send() never returns a partial write: it either
copies the entire buffer into the AFD queue (which grows to fit) and
returns the full length, or returns WSAEWOULDBLOCK only when the queue is
already full from a *previous* send. tryEnd() is the first write on a
fresh accepted socket, so it always reports success regardless of payload
size and pending_flush is never created — the handleRejectStream path
this test guards is POSIX-only by construction.

Reverts CHUNK to 8 MiB since the size was never the issue.
@Jarred-Sumner
Jarred-Sumner force-pushed the claude/ci-fixes-allocator-asan-windows branch from 208f832 to 943015a Compare April 29, 2026 06:55
@Jarred-Sumner
Jarred-Sumner merged commit 33e48bd into main Apr 29, 2026
22 of 39 checks passed
@Jarred-Sumner
Jarred-Sumner deleted the claude/ci-fixes-allocator-asan-windows branch April 29, 2026 07:04

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — test-only CI stabilization; my earlier nit is moot now that the fixture reverted to 8 MiB and the test is skipped on Windows instead.

Extended reasoning...

Overview

This PR touches five test files and no production code (the server.allocatorbun.default_allocator change referenced in the title already landed on main via #29916 and was merged into this branch). The net diff is:

  • serve-stream-reject-flush-leak-fixture.ts: whitespace-only reflow of a console.error call.
  • serve-stream-reject-flush-leak.test.ts: wrap in test.skipIf(isWindows) with a detailed comment explaining why pending_flush is unreachable on Winsock.
  • test-integration-rspack.ts: pin rsbuild@latestrsbuild@1 to avoid a double-mimalloc segfault on Windows arm64.
  • websocket-server.test.ts: stop truncating stderr to 3 lines so panic output is visible in failure diffs.
  • html-rewriter-leak.test.ts: rework the RSS measurement to GC per-pass and compare plateau RSS, with empirical before/after data in the description showing it both passes on fixed builds and still fails on pre-fix builds.

Security risks

None. All changes are confined to the test suite — no auth, crypto, parsing, or runtime code paths are touched. The rsbuild pin narrows rather than widens the dependency surface.

Level of scrutiny

Low. These are CI flake/stability fixes for pre-existing failures. The most substantive change is the html-rewriter-leak rewrite, but the PR description includes a 4-row matrix demonstrating the new methodology yields ~0 MB on fixed builds and ~68 MB on the pre-fix build, with the 25 MB threshold sitting comfortably between. Skipping the flush-leak test on Windows is acceptable since the leak is in platform-agnostic Zig and POSIX lanes still exercise it.

Other factors

My previous inline nit (stale "8 MiB" comment after a 64 MiB bump) has been resolved: commit 943015a dropped the 64 MiB approach in favor of skipIf(isWindows), so CHUNK is back at 8 MiB and the oneRequest() comment is accurate again. That inline thread is marked resolved. No outstanding human reviewer comments. The bug-hunting system found no issues.

xhjkl pushed a commit to xhjkl/bun that referenced this pull request May 14, 2026
…ven-sh#29926)

Clears four pre-existing failures showing up across open PRs.

## `server.allocator` → `bun.default_allocator`

`MimallocArena.getThreadLocalDefault()` and `bun.default_allocator` both
call mimalloc, but their vtables differ. Collections that flow between
server-owned and default-owned code (e.g. `BabyList` in the response
sink, the `onUpgrade` path) trip `CheckedAllocator.assertEq` in
`ci_assert` builds:

```
allocator mismatch: cannot use multiple allocators with the same collection
panic(main thread): Internal assertion failure: allocators do not match
```

This was crashing both `serve-stream-reject-flush-leak.test.ts` and the
websocket upgrade-UAF test on Windows release lanes. Same change as
oven-sh#29916 minus the X509/BIO hunks that already landed via oven-sh#29881.

## `html-rewriter-leak.test.ts` — RSS path never worked on release

The test was added in oven-sh#29879 and **failed on every release platform in
that PR's own CI** (113–233 MB across darwin/linux/windows/asan, all on
the *fixed* build). Only the debug path (precise mimalloc counters)
passed.

Root cause: a single trailing `Bun.gc(true)` does collect all 16k
rewriters, but neither mimalloc nor ASAN's allocator promptly return
freed pages to the OS, so RSS pins at the *peak* live set rather than
what's retained. Now GCs every 1k iterations to bound peak ≈ retained.

| build | before | after |
|---|---|---|
| release post-fix | 148 MB ❌ | 0.0 MB ✅ |
| asan post-fix | 233 MB ❌ | 0.6 MB ✅ |
| release **pre**-fix | — | 68.5 MB ❌ (still detects the leak) |
| debug post-fix | ✅ | ✅ |

## `serve-stream-reject-flush-leak.test.ts` — skip on Windows

Once the allocator panic is fixed, the test next fails on Windows with
`insufficient backpressure: 0/40` (oven-sh#29916 CI) — the 8 MiB `tryEnd()`
fits in Windows' auto-tuned loopback send buffer so `pending_flush` is
never created. The leak is platform-agnostic Zig; POSIX coverage is
sufficient.

## `websocket-server.test.ts` — un-truncate stderr

The upgrade-UAF test's `stderr.split("\\n", 3)` hid the panic line,
leaving `{stdout:"", stderr:""}` and a misleading diff.

## `test-integration-rspack.ts` — pin to `rsbuild@1`

`create-rsbuild@2.0.0` (Apr 22) pulls
`@rspack/binding-win32-arm64-msvc@2.0.x` which bundles mimalloc v3. Two
static mimalloc instances → deterministic segfault in ntdll during
`ExitProcess` on Windows arm64 (crash addr ends in `b9c8` across 5
builds). The test exists to guard the napi TSFN finalizer, not rsbuild
HEAD.

Supersedes oven-sh#29916.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants