Repository navigation
Memory leak in node::fs::ReadFileUtf8(const FunctionCallbackInfo<Value>& args) when args[0] is a string. #57800
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_openwhich callsfs__capture_path.fs__capture_pathallocates a UTF16 buffer and assigns it touv_fs_s.file.pathw.uv_fs_s.fileis 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.ReadFileUtf8callsuv_fs_readpassing in the file handlefile.uv_fs_readoverwrites the least significant half ofuv_fs_s.file.pathwwith this callreq->file.fd = fd;.- The pointer to the buffer formerly assigned to
uv_fs_s.file.pathwis lost.

Activity
juanarbol commentedon Apr 8, 2025
Humble ping to @nodejs/platform-windows and @nodejs/libuv
bnoordhuis commentedon Apr 9, 2025
At a quick look it's missing a
uv_fs_req_cleanup(&req)call afteruv_fs_open(). Untested but the fix probably looks like this:The
uv_fs_read()call further down below is also missing a concomitantuv_fs_req_cleanup(&req)call but that doesn't cause issues, if only by accident.tniessen commentedon Apr 9, 2025
@bnoordhuis There is a deferred
uv_fs_cleanup(&req)for when the file was opened successfully but I suppose that will only take care of the most recent value ofreq, which in this case likely is fromuv_fs_close()?node/src/node_file.cc
Lines 2720 to 2727 in 437c6aa
Before #49691, there was a separate
defer_req_cleanupas well as a separateuv_fs_t close_req.cc @anonrig since #49691 appears to have changed this part.
bnoordhuis commentedon Apr 9, 2025
@tniessen yes, it's tied to the
uv_fs_close()call; or if it isn't, it's still a bug becausereqgets repurposed foruv_fs_read()operations.Justin-Nietzel commentedon Apr 9, 2025
I tested this change locally too. It fixed the issue without any obvious side effects.
mcollina commentedon Apr 9, 2025
@bnoordhuis are you sending a PR?
bnoordhuis commentedon Apr 9, 2025
No. Feel free to take the diff, no attribution needed.
Justin-Nietzel commentedon Apr 9, 2025
I can make a PR with my branch. I'm re-running all the tests now.
Justin-Nietzel commentedon Apr 9, 2025
After pulling latest, something changed and started preventing me from running
vcbuild test. It worked when I first pulled the repo. I think something broke the test script if python is installed inC:/Program Files/because of the space in the path name. It's probably a me thing. I put together a draft PR for now and did my best to follow all of the contribution guidelines.Feel free to take over if I'm not doing this right. I'm not super familiar with working on open-source projects.
#57811
Is this issue with spaces in paths on Windows a known thing? Any way to get around it?
1 remaining item
fs: added test for missing call to uv_fs_req_cleanup
fs: add missing call to uv_fs_req_cleanup
fs: add missing call to uv_fs_req_cleanup
fs: added test for missing call to uv_fs_req_cleanup
fs: add missing call to uv_fs_req_cleanup
fs: added test for missing call to uv_fs_req_cleanup
fs: add missing call to uv_fs_req_cleanup
fs: added test for missing call to uv_fs_req_cleanup
fs: add missing call to uv_fs_req_cleanup
fs: added test for missing call to uv_fs_req_cleanup
fs: add missing call to uv_fs_req_cleanup
fs: added test for missing call to uv_fs_req_cleanup
fs: add missing call to uv_fs_req_cleanup
fs: added test for missing call to uv_fs_req_cleanup