Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow other specifications to create readable byte streams #1130

Merged
merged 9 commits into from
Jul 15, 2021
Merged

Conversation

@domenic domenic added the other spec interface How the Streams spec interfaces with other specifications label Jun 2, 2021
Copy link
Collaborator

@ricea ricea left a comment

Choose a reason for hiding this comment

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

lgtm

index.bs Outdated
1. Perform ! [$ReadableByteStreamControllerEnqueue$](|stream|.[=ReadableStream/[[controller]]=],
|uint8Array|).
1. Otherwise,
1. Write |bytes| into
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we need an assertion that it will fit?

Copy link
Member Author

Choose a reason for hiding this comment

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

Once we start using whatwg/webidl#987 there will be such an assertion transitively, but it can't hurt to add it here too.

@domenic
Copy link
Member Author

domenic commented Jun 4, 2021

Marking as "do not merge yet" pending #1121 and updating to use whatwg/webidl#987.

@domenic domenic added the do not merge yet Pull request must not be merged per rationale in comment label Jun 4, 2021
yutakahirano added a commit to w3c/webtransport that referenced this pull request Jun 28, 2021
Define pullAlgorithm and cancelAlgorithm, and set up ReceiveStream
with them.

This relies on whatwg/streams#1130.

Fixes #185.
@domenic
Copy link
Member Author

domenic commented Jun 29, 2021

This is ready to merge but we're getting some good feedback in w3c/webtransport#297 (comment) so I'll hold off a bit...

Copy link
Member Author

@domenic domenic left a comment

Choose a reason for hiding this comment

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

@yutakahirano, can you take a look at the latest commit? I should also update the Fetch and Web Serial PRs to make sure this works well for them too.

index.bs Outdated
1. If |byobRequest| is non-null, and |view|.\[[ViewedArrayBuffer]].\[[ArrayBufferData]] is the same
[=Data Block=] as |byobRequest|.\[[view]].\[[ViewedArrayBuffer]].\[[ArrayBufferData]], then:
1. Assert: |view|.\[[ByteOffset]] is |byobRequest|.\[[view]].\[[ByteOffset]].
1. Assert: |view|.\[[ByteLength]] ≤ |byobRequest|.\[[view]].\[[ByteLength]].
Copy link
Member Author

Choose a reason for hiding this comment

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

I made these asserts instead of if tests (compared to w3c/webtransport#297 (comment)) since it seems like it would be bad if the caller used the view but went past the requested bounds.

index.bs Outdated
|view|.\[[ByteLength]]).
1. Otherwise, perform ?
[$ReadableByteStreamControllerRespondWithNewView$](|stream|.[=ReadableStream/[[controller]]=],
|view|).
Copy link
Member Author

Choose a reason for hiding this comment

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

I handled the case where you transfer the backing Data Block here, which goes a bit beyond w3c/webtransport#297 (comment) but seems like a good idea.

Copy link
Member

@yutakahirano yutakahirano left a comment

Choose a reason for hiding this comment

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

Thank you, LGTM!

@domenic
Copy link
Member Author

domenic commented Jul 8, 2021

Almost done. Still TODO: update Fetch and Web Serial PRs for the new WebTransport-inspired model, to make sure this still works.

@domenic
Copy link
Member Author

domenic commented Jul 12, 2021

Seems to work. There's a bit of boilerplate if you, like the Web Serial and Fetch specs, already have a byte sequence on hand. Then you need to branch as to whether there's a BYOB request (and, in the case of Fetch, whether the byte sequence fits). I considered adding a helper for this, which would put us back to "enqueue bytes given a byte sequence", but I think it's OK to be more explicit. In particular it's more obviously optimizable by reading the spec in question, instead of having to go to Streams.

Still, to leave open the door for such a helper in the future, I'll rename the operation from "enqueue bytes" to "enqueue view".

Any final reviews, of either this, WICG/serial#137, or whatwg/fetch#1246 would be appreciated. If there aren't any, I'll merge this tomorrow-ish.

domenic and others added 4 commits July 12, 2021 17:07
Co-authored-by: Mattias Buelens <[email protected]>
Co-authored-by: Mattias Buelens <[email protected]>
Co-authored-by: Mattias Buelens <[email protected]>
@MattiasBuelens
Copy link
Collaborator

Okay, there's still an important problem. After a specification calls "close a readable stream" on a readable byte stream, the current BYOB request is still pending. We must somehow perform the equivalent of controller.byobRequest.respond(0) (like we do in our tests) to return ownership of the BYOB buffer and fulfill any pending byobReader.read(view) calls.

I suppose we have to extend "close a readable stream" to handle this? But then:

  • Is it safe to assume that, if the other specification calls "close a readable stream", then it is done using the current BYOB request view?
  • What if the other specification has transferred the current BYOB request view to a different realm? 😬

@domenic
Copy link
Member Author

domenic commented Jul 12, 2021

I think we can mandate that other specifications not transfer the current BYOB request view. Even if implementations do so (and then do the correspondingly-different ending dance), it wouldn't be observable, so we can write the specifications to assume they don't.

Similarly I think we can require that other specifications not write into the current BYOB request view after closing the readable stream.

Copy link
Collaborator

@MattiasBuelens MattiasBuelens left a comment

Choose a reason for hiding this comment

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

That seems reasonable. In that case, we need to do something similar as in the read request close steps of ReadableByteStreamTee. (See suggestion below.)

Co-authored-by: Mattias Buelens <[email protected]>
@domenic
Copy link
Member Author

domenic commented Jul 14, 2021

Alright, this is ready to merge, but since previous waiting periods have turned up great finds, I'll give it another day before doing so.

index.bs Outdated
1. If |stream|.[=ReadableStream/[[controller]]=].[=ReadableByteStreamController/[[pendingPullIntos]]=]
is not [=list/is empty|empty=], perform !
[$ReadableByteStreamControllerRespond$](|stream|.[=ReadableStream/[[controller]]=], 0).
<p class="note">The caller has to make sure not to write into the
Copy link
Collaborator

Choose a reason for hiding this comment

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

I feel some ambiguity here. How long before closing the stream is it okay to write into the view?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I think this does not match my understanding of #1130 (comment) . Reviewing that, I think instead we want to say something like:

  • The calling specification must never transfer the current BYOB request view. Note: implementations could do something equivalent to a transfer, but in that case they'd need to modify the steps in close to use RespondWithNewView instead of Respond(,0) to keep the same observable behavior as not transfering.
  • The calling specification must not write into the current BYOB request view after closing the stream.

@MattiasBuelens, WDYT?

Copy link
Collaborator

@MattiasBuelens MattiasBuelens Jul 14, 2021

Choose a reason for hiding this comment

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

The calling specification must never transfer the current BYOB request view. Note: implementations could do something equivalent to a transfer, but in that case they'd need to modify the steps in close to use RespondWithNewView instead of Respond(,0) to keep the same observable behavior as not transfering.

That's correct.

Do we want to provide an algorithm for closing a readable byte stream after the calling specification has already transferred the current BYOB request view? Or maybe add an optional newByobBuffer argument and switch on that to use either Respond(0) or RespondWithNewView(emptyViewOnNewByobBuffer)? (Kinda like the closeWithNewView() method I suggested in #1123.)

The calling specification must not write into the current BYOB request view after closing the stream.

Makes sense. If they write into the BYOB request, they should enqueue it first, and then close the stream.

To <dfn export for="ReadableStream">error</dfn> a {{ReadableStream}} |stream| given a JavaScript
value |e|:

1. If |stream|.[=ReadableStream/[[controller]]=] [=implements=] {{ReadableByteStreamController}},
Copy link
Collaborator

Choose a reason for hiding this comment

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

All this switching on controller type is unfortunate. If we had more types we might add some polymorphism to avoid it. But with only two types this is probably simpler.

index.bs Outdated
1. Let |byobRequest| be !
[$ReadableByteStreamControllerGetBYOBRequest$](|stream|.[=ReadableStream/[[controller]]=]).
1. If |byobRequest| is non-null, and |chunk|.\[[ViewedArrayBuffer]].\[[ArrayBufferData]] is the
same [=Data Block=] as |byobRequest|.\[[view]].\[[ViewedArrayBuffer]].\[[ArrayBufferData]],
Copy link
Collaborator

Choose a reason for hiding this comment

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

There may be a problem here. If the calling specification needs to transfer |byobRequest|.[[view]].[[ViewedArrayBuffer]] for some reason, then it will be detached and not match.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, but [[ArrayBufferData]] will still match. That's the hidden internal slot that the ECMAScript spec uses to abstract the concept of "memory regions", so it will survive such transfers.

Copy link
Collaborator

Choose a reason for hiding this comment

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

But if it's transferred it will be detached, right? And if it's detached then [[ArrayBufferData]] is set to null: https://tc39.es/ecma262/#sec-detacharraybuffer

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, dang, you're right. In that case we should probably solve it by, per the above discussion, prohibiting transferring at a spec level (but allowing it an implementation level as long as the implementation does appropriate behind-the-scenes contortions).

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think that's fine. If someone finds they need to transfer the buffer we can revisit.

Copy link
Collaborator

Choose a reason for hiding this comment

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

D'oh, how could I forget? I even explained this in #1123. 😛

We could make two separate algorithms: one for enqueuing a new chunk, and another for responding with a (transferred) BYOB request view. We still can't check if the given buffer is a transferred version of the BYOB request's buffer, so we'd have to rely on the calling specification to not mess this up.

Or yeah, we could just forbid transferring for now. 😁

Copy link
Collaborator

@ricea ricea left a comment

Choose a reason for hiding this comment

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

lgtm

@domenic domenic removed the do not merge yet Pull request must not be merged per rationale in comment label Jul 15, 2021
@domenic domenic merged commit bb53bec into main Jul 15, 2021
@domenic domenic deleted the setup-rbs branch July 15, 2021 18:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
other spec interface How the Streams spec interfaces with other specifications
Development

Successfully merging this pull request may close these issues.

Allow other specifications to create readable byte streams
4 participants