Repository navigation
Description
What version of Bun is running?
1.2.19
What platform is your computer?
• Linux (Ubuntu 22.04) • Also tested on macOS (M1 Pro)
What steps can reproduce the bug?
What’s happening?
I’m running a Bun child process with basically no logic inside it—just an interval that prints memory usage and sends a small meta object back to the parent every few seconds (via IPC).
At first, memory (RSS) in the child is around 110–115MB. Over the course of several hours, it drifts up to 140–150MB. This is without doing any real work:
• heapUsed and heapTotal stay flat,
• external is stable,
• Only RSS climbs (and never shrinks, even after forced GC).
This is not a parent memory leak, not a log buffer problem, and not a flood of IPC messages (the parent is always reading quickly).
GC doesn’t help—calling global.gc() has no effect on RSS.
How to reproduce (simple version):
bun-child-mem-test.ts:
setInterval(() => {
const mem = process.memoryUsage();
console.log(`[MEM] heapUsed=${(mem.heapUsed/1024/1024).toFixed(1)}MB | heapTotal=${(mem.heapTotal/1024/1024).toFixed(1)}MB | rss=${(mem.rss/1024/1024).toFixed(1)}MB | external=${(mem.external/1024/1024).toFixed(1)}MB`);
global.gc?.();
}, 3000);Run with:
bun --optimize --gc --expose-gc bun-child-mem-test.ts
What is the expected behavior?
• I’d expect RSS to stay relatively flat for an idle child process, or at least drop after GC if there’s nothing to collect.
• Node.js keeps its RSS low for a similar idle process, so this behavior seems unique to Bun.
What do you see instead?
• On Linux, RSS in the child process starts low (~110MB) but keeps slowly rising up to ~145MB and stays there, even though the JS heap numbers are stable.
• On macOS M1 Pro, the RSS is noticeably lower (almost half), but still rises a bit.
• This is not related to file descriptors or open files (checked with lsof—nothing weird).
• If you spawn multiple children, you see the same thing for each process (not a parent-side problem).
Additional information
Why it matters
• For apps that need to spawn a lot of child processes (or keep them running for days), this leads to unnecessary RAM usage over time and could be a problem in production.
• I’m not seeing this with Node.js, so it seems like a Bun-specific behavior.
Screenshots / logs
Here are some logs from a single child running overnight:
[01-08-2025 23:25:56.423] [MEM] heapUsed=32.8MB | heapTotal=9.6MB | rss=115.5MB | external=24.4MB
[02-08-2025 06:10:57.038] [MEM] heapUsed=29.9MB | heapTotal=9.2MB | rss=145.2MB | external=22.3MB
(and similar steady numbers all night, except for RSS which rises and never falls)
What I’ve already tried
• Forcing global.gc()
• Running with and without IPC, draining all stdio, no extra modules
• Same test on different OSes and hardware
TL;DR
• Child process just sends IPC meta and logs memory, but RSS keeps creeping up over hours, never drops, even if JS heap is totally flat.
• Not a userland leak, not a parent-side issue, not fixed by GC or different allocators.
• Seems unique to Bun.
Would love to know if this is expected, a known quirk of Bun/mimalloc, or maybe a deeper issue. Happy to help test more, or provide more logs if needed.
Thank you!
Activity
linear commentedon Aug 2, 2025
ENG-19895 Memory (RSS) in Bun Spawned Child Process Grows Slowly, Even When Idle
Jarred-Sumner commentedon Aug 2, 2025
Would love to help but this scenario is unfortunately over-simplified for us to take any action, as it does not demonstrate unbounded memory growth:
Also note that
--optimizeand--gcdon't do anything in Bun (we don't error because some people may pass flags to Bun meant for Node)acoBOYZ commentedon Aug 2, 2025
Hey, thanks for the detailed feedback and for checking my previous test!
To clarify:
I ran my child/parent setup for 6+ hours on Linux before, and the memory increase is what I was seeing in my production scenario, where the child process does real work and holds some heap (which is simulated here by allocating a static buffer).
Here’s a set of minimal test scripts that gets as close as possible to my real production structure (class-based, static heap, IPC via custom pipe-handler, etc):
logger.js
pipe-handler.js
parent-test.js
child-test.js
Results:
• With Pino: Adding Pino increases the baseline RSS by about 20MB (I agree, it’s pretty heavy for logging!). I’m planning to remove it from my codebase for production, or at least swap to raw logs for now.
• Without Pino: I still see a small, slow memory increase over hours, but it’s much smaller (a few MB over many hours), so it’s not a major leak anymore.
• In this setup, the heap allocation is static (no growth after startup), so any RSS growth is probably just from Bun’s event loop, IPC, or slow GC/allocator behavior.
If you want to reproduce, just drop these files in a folder and run bun parent-test.js with:
For my prod case, this setup is “close enough”—just imagine the heap is my real application objects.
⸻
Summary:
• Pino adds a big memory footprint, but even without it, a little slow RSS rise still exists.
• For most users this is probably fine, but for me (long-running daemon), even slow growth can become a problem over time.
• I’m okay with this level of growth for now, just wanted to report back with the most real-world code I could make.
• Thanks for the help, and happy to try or test anything else you want to check!
Jarred-Sumner commentedon Aug 2, 2025
ah in this case it's likely an issue with either IPC or maybe pino is quietly buffering something. setInterval is unrelated.
i suggest:
heapStats()frombun:jsconce RSS gets high enoughalso you could try setting
serializationto"json"acoBOYZ commentedon Aug 3, 2025
• Pino Removed:
I fully removed Pino and now only use a custom lightweight logger. This drops baseline RSS, but I still see a slow RSS increase over time.
• IPC Serialization Set to JSON:
Before moving on to more advanced tests, I switched Bun’s spawn IPC to serialization: "json" (not the default). This didn’t fix the RSS issue.
• Only RSS Increases:
Throughout multi-hour runs, only the rss value climbs (example: heapUsed=44.6MB | heapTotal=22.1MB | rss=218.4MB | external=25.7MB).
heapUsed, heapTotal, and external stay totally flat—no heap growth, no buffer leak.
• GC Has No Effect:
Manual GC (Bun.gc(true)) makes no difference to RSS behavior.
⸻
Major OS Difference:
• Linux (Ubuntu 24.04, x86_64):
RSS climbs steadily and just keeps going up over time. It ends up about 3x higher than macOS for the same workload. After rising, it never goes back down, even after forced GC or idle time.
• macOS (Sequoia 15.5, arm64):
RSS starts lower, rises slower, and the OS sometimes reclaims RSS and brings memory back down automatically. This never happens on Linux.
⸻
Conclusion / What I’m Seeing:
• The issue is not in JS/userland. Logging is minimal, serialization is JSON, and the heap stays flat.
• This seems like native memory (mimalloc, IPC/event loop, or something internal to Bun).
• The difference between Linux and macOS is huge for long-lived processes.
• This is a real problem for apps that need to run for days—Linux boxes use a lot more RAM over time.
⸻
Happy to provide (just show me the point):
• Heap snapshots (generateHeapSnapshot) at any point
• Native heap stats (MIMALLOC_SHOW_STATS=1)
• Logs or custom test cases
⸻
Questions for Bun Team:
• Is this slow RSS increase on Linux expected, or is it something you’re already tracking?
• Any way to make Bun or mimalloc return memory to the OS more aggressively, like macOS seems to do automatically?
⸻
Thanks for looking into this! Let me know if you want any other info or specific experiments.
new logger.js
Jarred-Sumner commentedon Aug 3, 2025
what if you try the canary build of bun?
bun upgrade --canary. we made some improvements to memory usage in certain scenarios.acoBOYZ commentedon Aug 3, 2025
Production Code Results (Bun 1.2.20 Canary, 1 hour, MIMALLOC_PURGE_DELAY=0):
Test Code Results (Bun 1.2.20 Canary, 1 hour, static heap allocation, MIMALLOC_PURGE_DELAY=0):
Notes:
-Bun 1.2.20 canary with MIMALLOC_PURGE_DELAY=0 has greatly reduced RSS growth, but the slow RSS rise is still only on Linux—macOS processes have RSS that gets reclaimed and returns to a lower, stable baseline.
Suggestions / What might help:
mi_collect(true)more aggressively, especially after each garbage collection cycle.Attaching latest test scripts below for reproducibility.
logger.js
setup-buffered-logs.js
pipe-handler.js
parent-test.js
child-test.js
melishev commentedon Aug 11, 2025
@acoBOYZ
I encountered the same problem in my project. RSS grows even if no load is created and reaches 1 GB, although it started at 70 MB. Perhaps you have found a solution at this point, maybe lowering the bun version?
@Jarred-Sumner I encountered a problem on bun 1.2.18. Updating to 1.2.20 did not fix the problem.
acoBOYZ commentedon Aug 11, 2025
Workaround (not fix):
I changed my system. I still use bun.spawn to start child process, but child is my own Go program. The Go program does my spawn logic again, and talks to my Bun “spawn master” with gRPC (not Bun IPC). With this, memory is lower, CPU is lower, and RSS is stable for long time. RSS is same on Linux and macOS for same process.
Future plan (maybe):
Later I may drop bun.spawn but keep bun service and run the workers inside one Go service using goroutines. For this I need to make strong isolation so one worker crash does not kill others. I will test both ways and pick what is best for my use case:
• isolation: per-worker context/cancel, panic recover, limits (CPU/mem) if possible, separate logs/metrics
• comms: keep gRPC API to Bun parent (stable contract)
• tests: run 24–48h soak tests on Linux + macOS, compare RSS/CPU, latency, and throughput
• choose the one with lower resources and better performance for my workload
Env: Ubuntu 24.04 x86_64 (kernel X.Y), Bun 1.2.20-canary, MIMALLOC_PURGE_DELAY=0. macOS Sequoia 15.5 arm64.
sangemaru commentedon Dec 4, 2025
For my part, if I have my orchestrator agent spawn a few parallel subagents, I can get that claude process (which relies on Bun) to baloon to 50GB of memory use real fast.
gastonmorixe commentedon Jan 20, 2026
it's is happening here as well to me. Will add more information after I gather more data.
Set MIMALLOC_PURGE_DELAY=0 to fix RSS growth from allocator page rete…
Skip Queue leak tests — caused by mimalloc page retention, not Effect…
fix(vscode): set MIMALLOC_PURGE_DELAY=0 on spawned kilo serve
jeremiahrhall commentedon May 7, 2026
Hi folks, Is this being worked on?
The mentioned Claude Code bug was closed without explanation. #27514 was closed as a duplicate of this by a bot but that's not a "spawned child process". I can reproduce RSS climbing despite GCs happening with one script, no child process. See repro here: https://github.com/jeremiahrhall/repro-bun-rss
For now, I cannot use Bun for production workloads due to this issue. We have a lot of excitement and things we love about Bun in my org, but with a memory leak like this that can't be fixed without patching Bun itself, going back to Node is the responsible option. We can check back in when this is sorted. Thankfully moving between the two isn't a herculean effort.
robobun commentedon May 12, 2026
Root-caused to
Bun.gc(true)never scavenging the native allocators after the JS GC runs finalizers:WTF::StringImplbodies land in libpas page caches and only decommit when the backgroundpas_scavengerthread gets around to itUnder sustained churn neither ever gets ahead, so RSS ratchets while the JS heap stays flat — exactly the "heapUsed flat, RSS climbing" pattern reported here and in #27514.
Reproduced with @jeremiahrhall's harness: post-
Bun.gc(true)RSS released goes from 0% → ~95% with the fix.PR: #30590
Move #21560 test under test/js/bun/util (not a true regression)
fix(vscode): set MIMALLOC_PURGE_DELAY=0 on spawned kilo serve
Mnigos commentedon Jul 12, 2026
@Jarred-Sumner We’re seeing the same native-allocator/RSS signature in a real, non-IPC ZIP/JSON workload. This appears to be additional evidence for the root cause identified in #30590, not a new diagnosis.
#30590 found that
Bun.gc(true)completed JavaScript GC but did not synchronously scavenge libpas or force mimalloc collection. That PR was closed because Bun’s Rust rewrite removed or reorganized the files it modified.Has the equivalent allocator-scavenging fix been implemented in the current codebase? I could not identify a merged successor from the issue or PR history.
Production measurements
Bun 1.3.3 on Linux:
Bun.gc(true)The workload downloads a ZIP into an
ArrayBuffer, inflates JSON entries, parses them, validates the complete history, and inserts it.Live JavaScript, external, and
ArrayBuffermemory returns close to baseline, but RSS does not.Local reproduction
A 300,000-record archive reproduced the same behavior:
ArrayBuffermemory returned to approximately 16 MBMoving each high-churn phase into a disposable process immediately returns the memory to the OS. After nine child executions, the long-lived parent grew by only approximately 3.8 MB.
This workaround strongly suggests allocator-retained pages rather than reachable JavaScript objects.
We have not yet repeated these measurements on Bun 1.3.14 or current canary. I can extract the workload into a standalone Linux reproduction that records:
process.memoryUsage()bun:jscheapStats()Is a current-tree port of #30590 the intended next step? Is there a branch or canary build you would like us to test?
docs(plan): open the macOS RSS retention unit with measured evidence
fix(vscode): set MIMALLOC_PURGE_DELAY=0 on spawned kilo serve
docs(plan): open the macOS RSS retention unit with measured evidence
renehonig commentedon Sep 29, 2026
Additional data point for the native-allocator RSS retention pattern on a long-lived Bun-compiled server binary (embedded Bun 1.4.2) on Linux x86_64 — the committed-but-not-returned signature here matches the libpas/mimalloc scavenging root-cause from #30590:
Workload: an
opencode serve --service-style server (Node/TypeScript server, JSC only, no workers, no native addons of note), 246 GiB host, no swap, no cgroup limits. Workload is agent-turn JSON traffic; when lanes are quiet the process sits at 250–900 MB for days.Ratchet: during active-turn windows, RSS jumps 981 MB → 25.6 GB within a single 5-minute sample (and 18.4 → 31.9 GB in another), then plateaus byte-stably for hours.
VmHWM(30.8 GB) > RSS (26 GB) — pages freed but not returned. One 8 GiB mapping holds a byte-identical 5.2 GB RSS across five 1-minute-apart readings. Partial decommit happens once (23.1 → 12.3 → 11.3 → 10.1 GB over ~2 days) and then stalls permanently.Not userland: a SIGUSR1-triggered heap snapshot while the process sat at 23.1 GB RSS shows the live JS heap is 172.2 MB (1.62 M nodes, 0 detached, 23.9 MB of strings; profile is routine module footprint).
smaps_rollup: 99.9% private-dirty anon; 21.7 GB of RSS sits in the JSC-allocator address band (~125× the live heap it hosts), with namedJSJITCode(10 MB RSS) andJSStructureHeap(~0 RSS) negligible. A control instance of the same binary on the same host with little traffic stays at 344 MB.So the allocator commits tens of GB to service transient JSON churn (parse + re-serialize of a few hundred MB of text, plausibly ×10–50 transiently) and never gives the pages back at bucket scale. Question for the team: at this scale (8 GiB buckets), is the "partial page clearing" path defeated by fragmentation, or is decommit simply not reached for these regions? I can run candidate builds — the repro is "server process with bursty large-JSON request handling; watch RSS vs live heap."
Happy to provide the RSS time series and smaps readings; the raw heapsnapshot contains in-process strings so it will not be shared — the aggregate sizes above are complete.
Application-side report with full anonymized evidence (RSS series, smaps rollup, snapshot aggregates): anomalyco/opencode#52041