Skip to content

Commit beb3742

Browse files
iskeracidiney
authored andcommitted
fs: make FileHandle.readableWebStream always create byte streams
The original implementation of the experimental `FileHandle.readableWebStream` API created non-`type: 'bytes'` streams, which prevented callers from creating `mode: 'byob'` readers from the returned stream, which means they could not achieve the associated "zero-copy" performance characteristics. Then, nodejs#46933 added a parameter allowing callers to pass the `type` parameter down to the ReadableStream constructor, exposing the same semantics to callers of `FileHandle.readableWebStream`. But there is no point to giving callers this choice: FileHandle-derived streams are by their very nature byte streams. We should not require callers to explicitly opt in to having byte stream semantics. Moreover, I do not see a situation in which callers would ever want to have a non-bytes stream: bytes-streams only do anything differently than normal ones if `mode: 'byob'` is passed to `getReader`. So, remove the `options` parameter and always create a ReadableStream with `type: 'bytes'`. Fixes nodejs#54041. PR-URL: nodejs#55461 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 05d7ed7 commit beb3742

File tree

3 files changed

+47
-95
lines changed

3 files changed

+47
-95
lines changed

doc/api/fs.md

+6-6
Original file line numberDiff line numberDiff line change
@@ -477,11 +477,14 @@ Reads data from the file and stores that in the given buffer.
477477
If the file is not modified concurrently, the end-of-file is reached when the
478478
number of bytes read is zero.
479479
480-
#### `filehandle.readableWebStream([options])`
480+
#### `filehandle.readableWebStream()`
481481
482482
<!-- YAML
483483
added: v17.0.0
484484
changes:
485+
- version: REPLACEME
486+
pr-url: https://github.com/nodejs/node/pull/55461
487+
description: Removed option to create a 'bytes' stream. Streams are now always 'bytes' streams.
485488
- version:
486489
- v20.0.0
487490
- v18.17.0
@@ -491,13 +494,10 @@ changes:
491494
492495
> Stability: 1 - Experimental
493496
494-
* `options` {Object}
495-
* `type` {string|undefined} Whether to open a normal or a `'bytes'` stream.
496-
**Default:** `undefined`
497-
498497
* Returns: {ReadableStream}
499498
500-
Returns a `ReadableStream` that may be used to read the files data.
499+
Returns a byte-oriented `ReadableStream` that may be used to read the file's
500+
contents.
501501
502502
An error will be thrown if this method is called more than once or is called
503503
after the `FileHandle` is closed or closing.

lib/internal/fs/promises.js

+31-36
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,10 @@ function lazyFsStreams() {
142142

143143
const lazyRimRaf = getLazy(() => require('internal/fs/rimraf').rimrafPromises);
144144

145+
const lazyReadableStream = getLazy(() =>
146+
require('internal/webstreams/readablestream').ReadableStream,
147+
);
148+
145149
// By the time the C++ land creates an error for a promise rejection (likely from a
146150
// libuv callback), there is already no JS frames on the stack. So we need to
147151
// wait until V8 resumes execution back to JS land before we have enough information
@@ -276,12 +280,9 @@ class FileHandle extends EventEmitter {
276280
/**
277281
* @typedef {import('../webstreams/readablestream').ReadableStream
278282
* } ReadableStream
279-
* @param {{
280-
* type?: string;
281-
* }} [options]
282283
* @returns {ReadableStream}
283284
*/
284-
readableWebStream(options = kEmptyObject) {
285+
readableWebStream(options = { __proto__: null, type: 'bytes' }) {
285286
if (this[kFd] === -1)
286287
throw new ERR_INVALID_STATE('The FileHandle is closed');
287288
if (this[kClosePromise])
@@ -293,46 +294,40 @@ class FileHandle extends EventEmitter {
293294
if (options.type !== undefined) {
294295
validateString(options.type, 'options.type');
295296
}
297+
if (options.type !== 'bytes') {
298+
process.emitWarning(
299+
'A non-"bytes" options.type has no effect. A byte-oriented steam is ' +
300+
'always created.',
301+
'ExperimentalWarning',
302+
);
303+
}
296304

297-
let readable;
298305

299-
if (options.type !== 'bytes') {
300-
const {
301-
newReadableStreamFromStreamBase,
302-
} = require('internal/webstreams/adapters');
303-
readable = newReadableStreamFromStreamBase(
304-
this[kHandle],
305-
undefined,
306-
{ ondone: () => this[kUnref]() });
307-
} else {
308-
const {
309-
ReadableStream,
310-
} = require('internal/webstreams/readablestream');
306+
const readFn = FunctionPrototypeBind(this.read, this);
307+
const ondone = FunctionPrototypeBind(this[kUnref], this);
311308

312-
const readFn = FunctionPrototypeBind(this.read, this);
313-
const ondone = FunctionPrototypeBind(this[kUnref], this);
309+
const ReadableStream = lazyReadableStream();
310+
const readable = new ReadableStream({
311+
type: 'bytes',
312+
autoAllocateChunkSize: 16384,
314313

315-
readable = new ReadableStream({
316-
type: 'bytes',
317-
autoAllocateChunkSize: 16384,
314+
async pull(controller) {
315+
const view = controller.byobRequest.view;
316+
const { bytesRead } = await readFn(view, view.byteOffset, view.byteLength);
318317

319-
async pull(controller) {
320-
const view = controller.byobRequest.view;
321-
const { bytesRead } = await readFn(view, view.byteOffset, view.byteLength);
318+
if (bytesRead === 0) {
319+
ondone();
320+
controller.close();
321+
}
322322

323-
if (bytesRead === 0) {
324-
ondone();
325-
controller.close();
326-
}
323+
controller.byobRequest.respond(bytesRead);
324+
},
327325

328-
controller.byobRequest.respond(bytesRead);
329-
},
326+
cancel() {
327+
ondone();
328+
},
329+
});
330330

331-
cancel() {
332-
ondone();
333-
},
334-
});
335-
}
336331

337332
const {
338333
readableStreamCancel,

test/parallel/test-filehandle-readablestream.js

+10-53
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,11 @@ const check = readFileSync(__filename, { encoding: 'utf8' });
8787
await file.close();
8888
})().then(common.mustCall());
8989

90-
// Make sure 'bytes' stream works
90+
// Make sure 'byob' reader works
9191
(async () => {
9292
const file = await open(__filename);
9393
const dec = new TextDecoder();
94-
const readable = file.readableWebStream({ type: 'bytes' });
94+
const readable = file.readableWebStream();
9595
const reader = readable.getReader({ mode: 'byob' });
9696

9797
let data = '';
@@ -114,59 +114,16 @@ const check = readFileSync(__filename, { encoding: 'utf8' });
114114
await file.close();
115115
})().then(common.mustCall());
116116

117-
// Make sure that acquiring a ReadableStream 'bytes' stream
118-
// fails if the FileHandle is already closed.
119-
(async () => {
120-
const file = await open(__filename);
121-
await file.close();
122-
123-
assert.throws(() => file.readableWebStream({ type: 'bytes' }), {
124-
code: 'ERR_INVALID_STATE',
125-
});
126-
})().then(common.mustCall());
127-
128-
// Make sure that acquiring a ReadableStream 'bytes' stream
129-
// fails if the FileHandle is already closing.
130-
(async () => {
131-
const file = await open(__filename);
132-
file.close();
133-
134-
assert.throws(() => file.readableWebStream({ type: 'bytes' }), {
135-
code: 'ERR_INVALID_STATE',
136-
});
137-
})().then(common.mustCall());
138-
139-
// Make sure the 'bytes' ReadableStream is closed when the underlying
140-
// FileHandle is closed.
141-
(async () => {
142-
const file = await open(__filename);
143-
const readable = file.readableWebStream({ type: 'bytes' });
144-
const reader = readable.getReader({ mode: 'byob' });
145-
file.close();
146-
await reader.closed;
147-
})().then(common.mustCall());
148-
149-
// Make sure the 'bytes' ReadableStream is closed when the underlying
150-
// FileHandle is closed.
151-
(async () => {
152-
const file = await open(__filename);
153-
const readable = file.readableWebStream({ type: 'bytes' });
154-
file.close();
155-
const reader = readable.getReader({ mode: 'byob' });
156-
await reader.closed;
157-
})().then(common.mustCall());
158-
159-
// Make sure that the FileHandle is properly marked "in use"
160-
// when a 'bytes' ReadableStream has been acquired for it.
117+
// Make sure a warning is logged if a non-'bytes' type is passed.
161118
(async () => {
162119
const file = await open(__filename);
163-
file.readableWebStream({ type: 'bytes' });
164-
const mc = new MessageChannel();
165-
mc.port1.onmessage = common.mustNotCall();
166-
assert.throws(() => mc.port2.postMessage(file, [file]), {
167-
code: 25,
168-
name: 'DataCloneError',
120+
common.expectWarning({
121+
ExperimentalWarning: [
122+
'A non-"bytes" options.type has no effect. A byte-oriented steam is ' +
123+
'always created.',
124+
],
169125
});
170-
mc.port1.close();
126+
const stream = file.readableWebStream({ type: 'foobar' });
127+
await stream.cancel();
171128
await file.close();
172129
})().then(common.mustCall());

0 commit comments

Comments
 (0)