Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
stream: implement finished() for ReadableStream and WritableStream
Refs: #39316
  • Loading branch information
debadree25 committed Jan 15, 2023
commit fd2fa5a362fa37c40756b78bb1c6de7936fb315a
23 changes: 20 additions & 3 deletions lib/internal/streams/end-of-stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const {
validateBoolean
} = require('internal/validators');

const { Promise } = primordials;
const { Promise, PromisePrototypeThen } = primordials;

const {
isClosed,
Expand All @@ -38,6 +38,15 @@ const {
willEmitClose: _willEmitClose,
} = require('internal/streams/utils');

const {
isBrandCheck,
} = require('internal/webstreams/util');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, this will break readable-stream I think you need to inverse the dependency and have webstream require symbols from stream.

FYI @mcollina

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As in having the symbols defined in internal/streams/util and webstreams requiring from there?

@ronag ronag Jan 15, 2023 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, we might even need to use a SymbolFor('nodejs.webstream.closed'). Similar to #45671.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok so would try to do the following, define SymbolFor('nodejs.webstream.closed') in internal/streams/util and use that instead of storing closed in the kState?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have implemented this could you check?


const isReadableStream =
Comment thread
ronag marked this conversation as resolved.
Outdated
isBrandCheck('ReadableStream');
const isWritableStream =
isBrandCheck('WritableStream');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we can use isBrandCheck due to readable-stream. @mcollina?

@debadree25 debadree25 Jan 15, 2023 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any alternatives? I had tried importing isReadableStream from internal/webstreams/readablestream but that makes the build fail

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check for expected signature? For example check for pipeThrough.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done removed the brandchecks based checking


function isRequest(stream) {
return stream.setHeader && typeof stream.abort === 'function';
}
Expand All @@ -62,8 +71,7 @@ function eos(stream, options, callback) {
const writable = options.writable ?? isWritableNodeStream(stream);

if (!isNodeStream(stream)) {
Comment thread
ronag marked this conversation as resolved.
Outdated
// TODO: Webstreams.
throw new ERR_INVALID_ARG_TYPE('stream', 'Stream', stream);
return eosWeb(stream, options, callback);
}

const wState = stream._writableState;
Expand Down Expand Up @@ -255,6 +263,15 @@ function eos(stream, options, callback) {
return cleanup;
}

function eosWeb(stream, opts, callback) {
Comment thread
RaisinTen marked this conversation as resolved.
Outdated
PromisePrototypeThen(
stream.streamClosed,
() => callback.call(stream),
(err) => callback.call(stream, err)
);
return nop;
}

function finished(stream, opts) {
let autoCleanup = false;
if (opts === null) {
Expand Down
10 changes: 5 additions & 5 deletions lib/internal/streams/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ function isWritableEnded(stream) {

// Have emitted 'finish'.
function isWritableFinished(stream, strict) {
if (!isWritableNodeStream(stream)) return null;
Comment thread
ronag marked this conversation as resolved.
Outdated
if (!isWritableNodeStream(stream)) return stream?.state === 'closed' ? true : null;
if (stream.writableFinished === true) return true;
const wState = stream._writableState;
if (wState?.errored) return false;
Expand All @@ -106,7 +106,7 @@ function isReadableEnded(stream) {

// Have emitted 'end'.
function isReadableFinished(stream, strict) {
if (!isReadableNodeStream(stream)) return null;
if (!isReadableNodeStream(stream)) stream?.state === 'closed' ? true : null;
const rState = stream._readableState;
if (rState?.errored) return false;
if (typeof rState?.endEmitted !== 'boolean') return null;
Expand Down Expand Up @@ -155,7 +155,7 @@ function isFinished(stream, opts) {

function isWritableErrored(stream) {
if (!isNodeStream(stream)) {
return null;
return stream?.state === 'errored' ? true : null;
}

if (stream.writableErrored) {
Expand All @@ -167,7 +167,7 @@ function isWritableErrored(stream) {

function isReadableErrored(stream) {
if (!isNodeStream(stream)) {
return null;
return stream?.state === 'errored' ? true : null;
}

if (stream.readableErrored) {
Expand All @@ -179,7 +179,7 @@ function isReadableErrored(stream) {

function isClosed(stream) {
if (!isNodeStream(stream)) {
return null;
return stream?.state === 'closed' ? true : null;
Comment thread
ronag marked this conversation as resolved.
Outdated
}

if (typeof stream.closed === 'boolean') {
Expand Down
14 changes: 13 additions & 1 deletion lib/internal/webstreams/readablestream.js
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,8 @@ class ReadableStream {
port1: undefined,
port2: undefined,
promise: undefined,
}
},
streamClosed: createDeferredPromise(),
Comment thread
ronag marked this conversation as resolved.
Outdated
};

// The spec requires handling of the strategy first
Expand Down Expand Up @@ -288,6 +289,12 @@ class ReadableStream {
return isReadableStreamLocked(this);
}

get streamClosed() {
if (!isReadableStream(this))
throw new ERR_INVALID_THIS('ReadableStream');
return this[kState].streamClosed.promise;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can't add new public properties to web streams. That is not allowed by the spec.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok updating this to private access?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have updated to access using [kState]

/**
* @param {any} [reason]
* @returns { Promise<void> }
Expand Down Expand Up @@ -1869,6 +1876,7 @@ function readableStreamCancel(stream, reason) {
function readableStreamClose(stream) {
assert(stream[kState].state === 'readable');
stream[kState].state = 'closed';
stream[kState].streamClosed?.resolve?.();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can streamClosed be nully?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it can because initially when I tested without null checks it failed

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then I think there might be a bug somewhere. What does it mean when it's nully?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay trying to investigate

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed this, removed the null checks basically it was breaking tests where tee() was being used to construct streams


const {
reader,
Expand Down Expand Up @@ -1900,6 +1908,10 @@ function readableStreamError(stream, error) {

reader[kState].close.reject(error);
setPromiseHandled(reader[kState].close.promise);
if (stream[kState].streamClosed?.promise !== undefined) {
stream[kState].streamClosed?.reject?.(error);
setPromiseHandled(stream[kState].streamClosed?.promise);
}

if (readableStreamHasDefaultReader(stream)) {
for (let n = 0; n < reader[kState].readRequests.length; n++)
Expand Down
14 changes: 13 additions & 1 deletion lib/internal/webstreams/writablestream.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,8 @@ class WritableStream {
port1: undefined,
port2: undefined,
promise: undefined,
}
},
streamClosed: createDeferredPromise(),
};

const size = extractSizeAlgorithm(strategy?.size);
Expand All @@ -201,6 +202,12 @@ class WritableStream {
return isWritableStreamLocked(this);
}

get streamClosed() {
if (!isWritableStream(this))
throw new ERR_INVALID_THIS('WritableStream');
return this[kState].streamClosed.promise;
}

/**
* @param {any} reason
* @returns {Promise<void>}
Expand Down Expand Up @@ -733,6 +740,10 @@ function writableStreamRejectCloseAndClosedPromiseIfNeeded(stream) {
writer[kState].close.reject?.(stream[kState].storedError);
setPromiseHandled(writer[kState].close.promise);
}
if (stream[kState].streamClosed?.promise !== undefined) {
stream[kState].streamClosed.reject?.(stream[kState]?.storedError);
setPromiseHandled(stream[kState].streamClosed?.promise);
}
}

function writableStreamMarkFirstWriteRequestInFlight(stream) {
Expand Down Expand Up @@ -839,6 +850,7 @@ function writableStreamFinishInFlightClose(stream) {
stream[kState].state = 'closed';
if (stream[kState].writer !== undefined)
stream[kState].writer[kState].close.resolve?.();
stream[kState].streamClosed?.resolve?.();
assert(stream[kState].pendingAbortRequest.abort.promise === undefined);
assert(stream[kState].storedError === undefined);
}
Expand Down
20 changes: 0 additions & 20 deletions test/parallel/test-stream-end-of-streams.js

This file was deleted.

7 changes: 1 addition & 6 deletions test/parallel/test-stream-finished.js
Original file line number Diff line number Diff line change
Expand Up @@ -260,12 +260,7 @@ const http = require('http');
const streamLike = new EE();
streamLike.readableEnded = true;
streamLike.readable = true;
assert.throws(
Comment thread
RaisinTen marked this conversation as resolved.
Outdated
() => {
finished(streamLike, () => {});
},
{ code: 'ERR_INVALID_ARG_TYPE' }
);
finished(streamLike, common.mustCall());
streamLike.emit('close');
}

Expand Down
40 changes: 40 additions & 0 deletions test/parallel/test-webstreams-finished.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
'use strict';

const common = require('../common');
const assert = require('assert');
const { ReadableStream, WritableStream } = require('stream/web');
const { finished } = require('stream');

{
const rs = new ReadableStream({
start(controller) {
controller.enqueue('asd');
controller.close();
},
});
finished(rs, common.mustSucceed());
async function test() {
const values = [];
for await (const chunk of rs) {
values.push(chunk);
}
assert.deepStrictEqual(values, ['asd']);
}
test();
}

{
let str = '';
const ws = new WritableStream({
write(chunk) {
console.log(chunk);
str += chunk;
}
});
finished(ws, common.mustSucceed(() => {
assert.strictEqual(str, 'asd');
}));
const writer = ws.getWriter();
writer.write('asd');
writer.close();
}