-
Notifications
You must be signed in to change notification settings - Fork 3k
Memory leak in Bun.spawn
with subprocess polling
#18265
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Slightly more detailed repro with const spawn = async (...args: string[]) => {
const proc = Bun.spawn([...args], {
stdio: ['ignore', 'pipe', 'ignore'],
});
const output = await Bun.readableStreamToText(proc.stdout);
let ret = false;
const lines = output.split('\n');
for (let i = 0; i < lines.length; i++) {
if (i === lines.length - 1) {
ret = true;
}
}
return ret;
};
const toMB = (bytes: number) => `${(bytes / 1024 ** 2).toFixed(1)} MB`;
let min = 0;
let max = 0;
let initial = 0;
while (true) {
// mimicking a small subprocess with minimal stdout output (called twice)
await spawn('sed', '5q', '/etc/passwd');
await spawn('sed', '10q', '/etc/passwd');
if (!initial) {
min = max = initial = process.memoryUsage().rss;
continue;
}
console.clear();
const { rss, heapUsed, heapTotal } = process.memoryUsage();
min = Math.min(min, rss);
max = Math.max(max, rss);
console.log(' Pid', process.pid);
console.log(' Heap Used', toMB(heapUsed));
console.log(' Heap Total', toMB(heapTotal));
console.log('Current RSS', toMB(rss));
console.log(' Min RSS', toMB(min));
console.log(' Max RSS', toMB(max));
await Bun.sleep(1000);
}
// btw `--inspect` crashes if i don't have imports/exports
export {}; I'll run this for a while and report back tomorrow. |
very interesting. thank you for the data. one thing that would help us narrow down the cause: does it happen if stdout, stderr, and stdin are all set to ignore? |
@Jarred-Sumner that's a great question. The short answer is no! The long answer with a bonus observation: I ran these two scripts in separate containers using
|
spawn-stdio-ignore | spawn-stdout-pipe |
---|---|
![]() |
![]() |
As you can see, with all stdio set to ignore
, the RSS kept hovering between the 40-50 MB range, while the the other script with stdout set to pipe
, it increased once again.
I also had the stdio-ignore script running on my mac but it died at some point, however last I checked the Activity Monitor was showing 13 MB under the "Memory" column after about an hour, which is super lean! This is the same 1.39 GB value that I mentioned in my previous comment.

Now, what's interesting is that the heap total/used numbers are completely different between the two scripts (both screenshots capture the same duration):
spawn-stdio-ignore | spawn-stdout-pipe |
---|---|
![]() |
![]() |
With stdio set to ignore
the heap numbers are much nicer and very consistent! With stdout set to pipe
the heap used is all over the place and, unlike the other one, it's higher than the total heap which did not make sense to me at all. I've noticed this too in my previous tests.
I'm still experiencing a memory leak in Please refer to #18316 (comment) for more details. Can we please reopen this issue? |
Just to rule it out, can you give it a run with |
What version of Bun is running?
1.2.6-canary.74+74768449b
What platform is your computer?
Linux 6.1.62 x86_64 / Darwin 24.3.0 arm64
What steps can reproduce the bug?
I have been noticing an unusual increase in memory usage with my app and after days of debugging I was able to narrow it down to
Bun.spawn
being the culprit.The app polls a short-lived subprocess twice every second and reads its output. The subprocess is very small and quick (the output is ~600 bytes and execution time is ~30ms). Over an extended period of time (~12 hours), the RSS grows significantly, exceeding 1GB. The heap stats stay relatively stable although very slowly trending upwards.
This is unexpected for a lightweight polling operation and suggests that subprocess handles, output buffers, or event listeners are not being properly cleaned up, preventing garbage collection. I started monitoring
process.memoryUsage()
and here's the readings I got on two different instances:Repro steps:
Note
I added a detailed repro script here and shared the memory readings from it here.
To further validate this, I kept this snippet bellow running overnight:
And here's the readings I got after 12 hours:
You can also play around the with sleep duration and see how the memory grows, although it might not happen immediately as you can see in the graphs above. You might want to keep it running for few hours or overnight (in which it would be helpful to log the max RSS too).
I understand RSS is not necessarily an indication of memory leaks, but even when I look the overall memory consumption of my app process from the host system, I see that it reaches around 1.5GB after a while, even for this small repro above.
What is the expected behavior?
Memory usage should stabilize over time.
I ran the repro snippet above with node v23.8.0 using
child_process.spawn
side by side with Bun (which btw Bun did use waaaaaaay less heap) for the same duration, and this is the reading I got:What do you see instead?
Memory grows unbounded to over 1GB
Additional information
If this is not a "memory leak" in the classical sense, could you please me help understand what it is and why I don't get similar RSS readings with other runtimes?
The text was updated successfully, but these errors were encountered: