nodejs / node

Node.js JavaScript runtime ✨🐢🚀✨
https://nodejs.org
Other
107.68k stars 29.64k forks source link

AbortSignal.any() causes memory leak #54614

Open Furzel opened 2 months ago

Furzel commented 2 months ago

Version

v22.6.0

Platform

Microsoft Windows NT 10.0.22631.0 x64
Linux ****** 4.4.0-22621-Microsoft #3672-Microsoft Fri Jan 01 08:00:00 PST 2016 x86_64 x86_64 x86_64 GNU/Linux

Subsystem

https://nodejs.org/api/globals.html#class-abortsignal

What steps will reproduce the bug?

Run this and watch memory usage.

const formatMemoryUsage = (data) => `${Math.round(data / 1024 / 1024 * 100) / 100} MB`;

let memoryData = process.memoryUsage();
console.log('Mem before loop', formatMemoryUsage(memoryData.rss));

for (let i = 0; true; i++) {
    const abortController = new AbortController();
    const signal = abortController.signal;
    const composedSignal = AbortSignal.any([signal]);

    if (i === 1000000) {
        break;
    }
}

memoryData = process.memoryUsage();
console.log('Mem after 1 million iteration', formatMemoryUsage(memoryData.rss));

This is what I get on my local machine image

How often does it reproduce? Is there a required condition?

Always reproducible as far as I can tell

What is the expected behavior? Why is that the expected behavior?

Memory post loop execution should be fairly equivalent to the first log but somehow the const composedSignal = AbortSignal.any([signal]); does not get cleaned up from memory, I would expect this to get cleaned properly or if this is the intended behavior to have a clear warning in the documentation.

What do you see instead?

We see a memory leak that will eventually lead to an out of memory error.

Additional information

This has been tested on Node 22.6 on different machine and both Windows + Unix versions. Happy to provide more details if needed

kallerosenbaum commented 2 months ago

This might (I'm not sure) be a duplicate of https://github.com/nodejs/node/issues/48419

RedYetiDev commented 2 months ago
$ node repro.js 
Mem before loop 44.25 MB
Mem after 1 million iteration 1375.11 MB
geeksilva97 commented 1 month ago

I started taking some snapshot

image image

@RedYetiDev would you have any clue?

mika-fischer commented 1 month ago

I think the issue here is that everything is happening in the same tick. This does not leak for me:

const formatMemoryUsage = (data) => `${Math.round((data / 1024 / 1024) * 100) / 100} MB`;

let memoryData = process.memoryUsage();
console.log('Mem before loop', formatMemoryUsage(memoryData.rss));

let i = 0;
function run() {
    const abortController = new AbortController();
    const signal = abortController.signal;
    const composedSignal = AbortSignal.any([signal]);

    if (i === 1000000) {
        memoryData = process.memoryUsage();
        console.log('Mem after 1 million iteration', formatMemoryUsage(memoryData.rss));
    } else {
        i++;
        setImmediate(run);
    }
}

run();