Skip to content

Commit 07d82c1

Browse files
committed
fs: fix close listener leak in fs streams
1 parent d3ed6c1 commit 07d82c1

2 files changed

Lines changed: 37 additions & 1 deletion

File tree

lib/internal/fs/streams.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,23 @@ function importFd(stream, options) {
152152
stream[kHandle] = options.fd;
153153
stream[kFs] = FileHandleOperations(stream[kHandle]);
154154
stream[kHandle][kRef]();
155-
options.fd.on('close', FunctionPrototypeBind(stream.close, stream));
155+
156+
const onclose = FunctionPrototypeBind(stream.close, stream);
157+
options.fd.on('close', onclose);
158+
if (options.autoClose === false) {
159+
function cleanup() {
160+
const handle = stream[kHandle];
161+
if (handle === null)
162+
return;
163+
stream[kHandle] = null;
164+
handle.removeListener('close', onclose);
165+
handle[kUnref]();
166+
}
167+
stream.once('end', cleanup);
168+
stream.once('finish', cleanup);
169+
stream.once('error', cleanup);
170+
}
171+
156172
return options.fd.fd;
157173
}
158174

test/parallel/test-fs-promises-file-handle-stream.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,27 @@ async function validateRead() {
4242
);
4343
}
4444

45+
async function validateReusedCreateReadStream() {
46+
const filePath = path.resolve(tmpDir, 'tmp-reused-stream.txt');
47+
fs.writeFileSync(filePath, Buffer.alloc(11, 0));
48+
49+
const fileHandle = await open(filePath, 'r');
50+
try {
51+
for (let i = 0; i < 11; i++) {
52+
await buffer(fileHandle.createReadStream({
53+
start: i,
54+
end: i,
55+
autoClose: false,
56+
}));
57+
assert.strictEqual(fileHandle.listenerCount('close'), 0);
58+
}
59+
} finally {
60+
await fileHandle.close();
61+
}
62+
}
63+
4564
Promise.all([
4665
validateWrite(),
4766
validateRead(),
67+
validateReusedCreateReadStream(),
4868
]).then(common.mustCall());

0 commit comments

Comments
 (0)