Skip to content
Open
Changes from 3 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
32 changes: 23 additions & 9 deletions test/js/web/fetch/fetch-leak.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,10 @@
kind,
async () => {
const script = `
const payload = Buffer.alloc(128 * 1024, 0x61); // 128 KiB of 'a'
// 16 KiB bodies: cycle() keeps 512 alive at once and gc() may not have returned that
// memory to the OS by the time rss() is sampled, so one cycle's live set (8 MiB here)
// is the noise floor of this measurement and must stay far below the threshold.
const payload = Buffer.alloc(16 * 1024, 0x61); // 16 KiB of 'a'
const str = payload.toString("latin1");
const sharedBlob = new Blob([payload]);
const rss = process.platform === "darwin" && typeof Bun.unsafe.memoryFootprint === "function" ? Bun.unsafe.memoryFootprint : process.memoryUsage.rss;
Expand Down Expand Up @@ -705,18 +708,25 @@

const deltaMB = (final - baseline) / 1024 / 1024;
console.log(JSON.stringify({ kind: "${kind}", baselineMB: (baseline / 1024 / 1024) | 0, finalMB: (final / 1024 / 1024) | 0, deltaMB: Math.round(deltaMB) }));
// 32 cycles * 512 Requests * 128 KiB = 2 GiB through the pool. If deinit() is
// skipped for any heap-backed variant, RSS climbs by ~2 GiB; with the
// 32 cycles * 512 Requests * 16 KiB = 256 MiB through the pool. If deinit() is
// skipped for any heap-backed variant, RSS climbs by ~256 MiB; with the
// Zig semantics it stays flat. 64 MiB is well above GC/allocator noise.
// ASAN's quarantine retains freed allocations so widen the threshold there.
if (deltaMB > ${isASAN ? 320 : 64}) {
// The ASAN bound must also stay below that 256 MiB signal; the child's
// quarantine is capped (env below) so freed churn recycles, and 128 MiB
// covers what ASAN still retains (observed delta ~6 MiB).
if (deltaMB > ${isASAN ? 128 : 64}) {
throw new Error("Request body (${kind}) leaked " + Math.round(deltaMB) + " MB over 32 cycles of 512 Requests");
}
`;

await using proc = Bun.spawn({
cmd: [bunExe(), "--smol", "-e", script],
env: bunEnv,
env: {
...bunEnv,
// Cap the quarantine so it cannot retain freed bodies toward the
// default 256 MB, which would cross the 128 MiB leak threshold.
...(isASAN && { ASAN_OPTIONS: `${bunEnv.ASAN_OPTIONS ?? ""}:quarantine_size_mb=32`.replace(/^:/, "") }),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
},
stdout: "pipe",
stderr: "pipe",
});
Expand All @@ -742,9 +752,13 @@
env: {
...bunEnv,
SERVER_URL: server.url.href,
// ASAN's quarantine retains freed allocations so RSS stays elevated
// under bun-asan; the fixture only allows MAX_MEMORY_INCREASE MiB.
MAX_MEMORY_INCREASE: isASAN ? "64" : "5", // in MB
// The regression this guards retained the 128 KiB body per request, i.e.
// ~32 MiB over the 250 sampled iterations; RSS of a non-leaking run still
// drifts over that window (allocator high-water, lazily freed pages):
// a few MiB on Linux, up to 14 observed on Windows lanes. The allowance
// sits between that drift and the 32 MiB signal. ASAN's quarantine
// retains freed allocations so RSS stays elevated under bun-asan.
MAX_MEMORY_INCREASE: isASAN ? "64" : "20", // in MB

Check failure on line 761 in test/js/web/fetch/fetch-leak.test.ts

View check run for this annotation

Claude / Claude Code Review

readable-stream ASAN allowance (64 MiB) exceeds the ~32 MiB leak signal

The ASAN branch here is still `"64"`, but the new comment two lines up computes the guarded regression's signal as ~32 MiB — so under `bun-asan` the reintroduced #23697 leak (~36-40 MiB with the capped quarantine) would pass this check. This is the same class as the HiveRef 320 > 256 threshold this PR just fixed in 23d51ac; the ASAN allowance here should also sit below its 32 MiB signal (e.g. `isASAN ? "24" : "20"`, or drop the branch since quarantine is already capped).
Comment thread
robobun marked this conversation as resolved.
Outdated
// The fixture asserts RSS stabilizes after iteration 250, but with the
// default 256 MB quarantine the freed 128 KB bodies are never reused and
// RSS keeps climbing through all 500 iterations (~97 MB past the sample
Comment thread
robobun marked this conversation as resolved.
Expand Down