-
Notifications
You must be signed in to change notification settings - Fork 29.6k
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
High memory consumption when piping between streams #50762
Comments
I tried on I modified a little bit the script to print the memory. import { createReadStream, createWriteStream } from 'fs';
import { memoryUsage } from 'process';
const srcPath = './large-file.txt'
const dstPath = '/tmp/test-destination-stream'
const srcStream = createReadStream(srcPath)
const dstStream = createWriteStream(dstPath)
dstStream.on('drain', () => {
// Simulates a slow WriteStream
setTimeout(() => {
srcStream.resume()
}, 20)
})
srcStream.on('end', () => {
console.log("srcStream.on('end')");
dstStream.end()
})
let i = 0
srcStream.on('data', (chunk) => {
dstStream.write(chunk, (error) => {
// No errors occur, even if destination file is removed
if (error) console.error(error);
})
srcStream.pause()
console.log('on(data)', i++)
})
console.log('Memory usage: ' + memoryUsage().heapTotal / (1024 * 1024));
let j = 0
setInterval(() => {
console.log('Memory usage: ' + memoryUsage().heapTotal / (1024 * 1024));
console.log('Keeping process alive', j++)
}, 3000);
I use the following command to create the file: dd if=/dev/random of=./large-file.txt bs=4k iflag=fullblock,count_bytes count=100MB |
Thanks for your execution. I tried it here and I think The output for
(some texts are in Portuguese, I couldn't change them to English) Full recording: I modified your code slightly: import { createReadStream, createWriteStream } from 'fs';
import { memoryUsage } from 'process';
console.log('Waiting 5s to start...');
setTimeout(() => {
console.log('Starting');
const srcPath = '/tmp/large-file.txt'
const dstPath = '/tmp/test-destination-stream'
const srcStream = createReadStream(srcPath)
const dstStream = createWriteStream(dstPath)
dstStream.on('drain', () => {
// Simulates a slow WriteStream
setTimeout(() => {
srcStream.resume()
}, 8)
})
srcStream.on('end', () => {
console.log("srcStream.on('end')");
dstStream.end()
})
let i = 0
srcStream.on('data', (chunk) => {
dstStream.write(chunk, (error) => {
// No errors occur, even if destination file is removed
if (error) console.error(error);
})
srcStream.pause()
if (i++ % 300 === 0) console.log('on(data)', i)
})
console.log('Memory usage: ' + memoryUsage().heapTotal / (1024 * 1024));
let j = 0
setInterval(() => {
console.log('Memory usage: ' + memoryUsage().heapTotal / (1024 * 1024));
console.log('Keeping process alive', j++)
}, 3000);
}, 5000); |
I'm br, no worries hahaha About the Does it mean is leaking even with empty files? Maybe, or no, I don't know how this I also tried using The memory "leaked" is way less than before, so the newer version of Node is using more memory I think but I don't have idea if is leaking or not. |
I modified a little bit the script to include the import { createReadStream, createWriteStream } from 'fs';
import { memoryUsage } from 'process';
console.log('Waiting 5s to start...');
setTimeout(() => {
console.log('Starting');
const srcPath = './large-file.txt'
const dstPath = '/tmp/test-destination-stream'
let srcStream = createReadStream(srcPath)
let dstStream = createWriteStream(dstPath)
dstStream.on('drain', () => {
// Simulates a slow WriteStream
setTimeout(() => {
srcStream.resume()
}, 8)
})
srcStream.on('end', () => {
console.log("srcStream.on('end')");
dstStream.end()
})
let i = 0
srcStream.on('data', (chunk) => {
dstStream.write(chunk, (error) => {
// No errors occur, even if destination file is removed
if (error) console.error(error);
})
srcStream.pause()
if (i++ % 300 === 0) console.log('on(data)', i)
})
console.log('Memory usage: ' + memoryUsage().heapTotal / (1024 * 1024));
let j = 0
setInterval(() => {
console.log('Memory usage: ' + memoryUsage().heapTotal / (1024 * 1024));
console.log('Keeping process alive', j++)
}, 3000);
process.on('exit', () => {
srcStream.close();
dstStream.close();
srcStream = null;
dstStream = null;
global.gc();
})
}, 5000); You can run with: heaptrack node --expose-gc test-memory-2.mjs So, I think is not leaking, is just V8 not calling the GC when is not needed, if we force GC, it's okay. On 20.9.0 still a little bit high but at least in the newer version is ok. |
Awesome! Thanks, @H4ad. (BTW, I saw that you are Brazilian ;)) I tested here and had the same results: With that in mind, I renamed the issue. It's not as critical as I thought. Anyway, Node doesn't release the used memory after the pipe finishes. It only does that if you call the pipe method ( |
It's an expected behavior, if you want the pipe to be closed after it finishes, you should use pipeline.
Well, this is more about V8 than node itself, I only use the Maybe using
About this, I think we can close this issue and you can share your thoughts/findings at nodejs/performance#117, we are already trying to optimize streams, so more data/insights are always helpful. |
Hello. Even using pipeline, when the read stream finishes and audio stops playing, memory consumption keeps going up. This is a bug to me. |
Version
Both v18.17.0 and v20.9.0
Platform
Linux (hostname-ommitted) 6.2.0-36-generic #37~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Mon Oct 9 15:34:04 UTC 2 x86_64 x86_64 x86_64 GNU/Linux
Subsystem
No response
What steps will reproduce the bug?
How often does it reproduce? Is there a required condition?
No. It always happens. By the way, I tried the ReadStream.pipe method, pipeline function and the manual piping above with readStream.on('data') and writeStream.on('drain'). All presented the same results.
What is the expected behavior? Why is that the expected behavior?
Memory consumption should remain constant, approximately the size of a buffer, or little more than that.
This is because there's no reason to store old, consumed buffers in memory once they have already been written to the output.
What do you see instead?
Memory consumption is ever-increasing until the end of the pipe operation. After it ends, no memory is released. So, there are two problems here: 1) high memory consumption during the pipe operation; 2) memory leak after the operation finishes.
Additional information
I found this while working with the
aplay
program on Linux to play sounds. However, I changed the example program to simply copy a file from input to output path, and it still happens.The text was updated successfully, but these errors were encountered: