Skip to content

Commit b9586bf

Browse files
jasnelladuh95
authored andcommitted
fs: add autoClose option to FileHandle readableWebStream
By default, the `readableWebStream` method of `FileHandle` returns a ReadableStream that, when finished, does not close the underlying FileHandle. This can lead to issues if the stream is consumed without having a reference to the FileHandle to close after use. This commit adds an `autoClose` option to the `readableWebStream` method, which, when set to `true`, will automatically close the FileHandle when the stream is finished or canceled. The test modified in this commit demonstrates one of the cases where this is necessary in that the stream is consumed by separate code than the FileHandle which was being left to close the underlying fd when it is garbage collected, which is a deprecated behavior. PR-URL: #58548 Reviewed-By: LiviaMedeiros <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]>
1 parent 253772c commit b9586bf

File tree

4 files changed

+45
-13
lines changed

4 files changed

+45
-13
lines changed

doc/api/fs.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -476,14 +476,17 @@ Reads data from the file and stores that in the given buffer.
476476
If the file is not modified concurrently, the end-of-file is reached when the
477477
number of bytes read is zero.
478478
479-
#### `filehandle.readableWebStream()`
479+
#### `filehandle.readableWebStream([options])`
480480
481481
<!-- YAML
482482
added: v17.0.0
483483
changes:
484484
- version: REPLACEME
485485
pr-url: https://github.com/nodejs/node/pull/57513
486486
description: Marking the API stable.
487+
- version: REPLACEME
488+
pr-url: https://github.com/nodejs/node/pull/58548
489+
description: Added the `autoClose` option.
487490
- version: v22.15.0
488491
pr-url: https://github.com/nodejs/node/pull/55461
489492
description: Removed option to create a 'bytes' stream. Streams are now always 'bytes' streams.
@@ -494,6 +497,9 @@ changes:
494497
description: Added option to create a 'bytes' stream.
495498
-->
496499
500+
* `options` {Object}
501+
* `autoClose` {boolean} When true, causes the {FileHandle} to be closed when the
502+
stream is closed. **Default:** `false`
497503
* Returns: {ReadableStream}
498504
499505
Returns a byte-oriented `ReadableStream` that may be used to read the file's

lib/internal/fs/promises.js

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ const {
8484
validateEncoding,
8585
validateInteger,
8686
validateObject,
87-
validateString,
8887
kValidateObjectAllowNullable,
8988
} = require('internal/validators');
9089
const pathModule = require('path');
@@ -278,9 +277,10 @@ class FileHandle extends EventEmitter {
278277
/**
279278
* @typedef {import('../webstreams/readablestream').ReadableStream
280279
* } ReadableStream
280+
* @param {{ type?: 'bytes', autoClose?: boolean }} [options]
281281
* @returns {ReadableStream}
282282
*/
283-
readableWebStream(options = { __proto__: null, type: 'bytes' }) {
283+
readableWebStream(options = kEmptyObject) {
284284
if (this[kFd] === -1)
285285
throw new ERR_INVALID_STATE('The FileHandle is closed');
286286
if (this[kClosePromise])
@@ -289,20 +289,27 @@ class FileHandle extends EventEmitter {
289289
throw new ERR_INVALID_STATE('The FileHandle is locked');
290290
this[kLocked] = true;
291291

292-
if (options.type !== undefined) {
293-
validateString(options.type, 'options.type');
294-
}
295-
if (options.type !== 'bytes') {
292+
validateObject(options, 'options');
293+
const {
294+
type = 'bytes',
295+
autoClose = false,
296+
} = options;
297+
298+
validateBoolean(autoClose, 'options.autoClose');
299+
300+
if (type !== 'bytes') {
296301
process.emitWarning(
297302
'A non-"bytes" options.type has no effect. A byte-oriented steam is ' +
298303
'always created.',
299304
'ExperimentalWarning',
300305
);
301306
}
302307

303-
304308
const readFn = FunctionPrototypeBind(this.read, this);
305-
const ondone = FunctionPrototypeBind(this[kUnref], this);
309+
const ondone = async () => {
310+
this[kUnref]();
311+
if (autoClose) await this.close();
312+
};
306313

307314
const ReadableStream = lazyReadableStream();
308315
const readable = new ReadableStream({
@@ -314,15 +321,15 @@ class FileHandle extends EventEmitter {
314321
const { bytesRead } = await readFn(view, view.byteOffset, view.byteLength);
315322

316323
if (bytesRead === 0) {
317-
ondone();
318324
controller.close();
325+
await ondone();
319326
}
320327

321328
controller.byobRequest.respond(bytesRead);
322329
},
323330

324-
cancel() {
325-
ondone();
331+
async cancel() {
332+
await ondone();
326333
},
327334
});
328335

test/es-module/test-wasm-web-api.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,9 @@ function testCompileStreamingRejectionUsingFetch(responseCallback, rejection) {
106106
// Response whose body is a ReadableStream instead of calling fetch().
107107
await testCompileStreamingSuccess(async () => {
108108
const handle = await fs.open(fixtures.path('simple.wasm'));
109-
const stream = handle.readableWebStream();
109+
// We set the autoClose option to true so that the file handle is closed
110+
// automatically when the stream is completed or canceled.
111+
const stream = handle.readableWebStream({ autoClose: true });
110112
return Promise.resolve(new Response(stream, {
111113
status: 200,
112114
headers: { 'Content-Type': 'application/wasm' }
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import '../common/index.mjs';
2+
import { open } from 'node:fs/promises';
3+
import { rejects } from 'node:assert';
4+
5+
{
6+
const fh = await open(new URL(import.meta.url));
7+
8+
// TODO: remove autoClose option when it becomes default
9+
const readableStream = fh.readableWebStream({ autoClose: true });
10+
11+
// Consume the stream
12+
await new Response(readableStream).text();
13+
14+
// If reading the FileHandle after the stream is consumed fails,
15+
// then we assume the autoClose option worked as expected.
16+
await rejects(fh.read(), { code: 'EBADF' });
17+
}

0 commit comments

Comments
 (0)