Description
Version
v22.14.0
Platform
Microsoft Windows NT 10.0.19045.0 x64
Subsystem
node::fs
What steps will reproduce the bug?
Open a file with ReadFileUtf8() using a path (not a file handle).
How often does it reproduce? Is there a required condition?
Every time, including loading JavaScript files for execution.
What is the expected behavior? Why is that the expected behavior?
Calling readFileSync()
with an encoding of UTF-8 should not permanently increase the amount of rss memory being used by node. It's (hopefully) the expected behavior because memory leaks are bad.
What do you see instead?
Calling will cause Node's memory usage to go up indefinitely.
This was discovered when a process using node:worker_threads
would continuously increase it's memory consumption until it was forcibly closed. JavaScript debug tools showed that the managed heap's memory usage was constant, but the rss memory usage kept rising. Debugging the node runtime showed that loading modules was calling the underlying ReadFileUtf8
method. The process that was showing this memory leak was not caching workers and was re-loading all code files each time a worker thread was created.
Minimum code example:
import { readFileSync } from 'node:fs';
const options = { encoding: 'utf-8' };
for (let i = 1; true; i++) {
readFileSync('./empty.txt', options);
if (i % 1000 === 0) {
i = 0;
gc();
}
}
Output from process.memoryUsage()
:
2025-04-08 20:23:38: rss - 357.03 MB
2025-04-08 20:23:38: heapTotal - 7.03 MB
2025-04-08 20:23:38: heapUsed - 4.28 MB
2025-04-08 20:23:38: external - 1.73 MB
Additional information
This is my best guess at the bug. I'm like 85% sure of what's going, but this is the first time I'm looking at the NodeJS codebase.
Allocation:
void ReadFileUtf8(const FunctionCallbackInfo<Value>& args)
callsuv_fs_open
which callsfs__capture_path
.fs__capture_path
allocates a UTF16 buffer and assigns it touv_fs_s.file.pathw
.uv_fs_s.file
is a union type with the definition:union { WCHAR* pathw; int fd; }
Corruption
uv_file file;
is allocated and assigned the return value fromuv_fs_open
.ReadFileUtf8
callsuv_fs_read
passing in the file handlefile
.uv_fs_read
overwrites the least significant half ofuv_fs_s.file.pathw
with this callreq->file.fd = fd;
.- The pointer to the buffer formerly assigned to
uv_fs_s.file.pathw
is lost.