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

Support transferable streams (postMessage) #1053

Merged
merged 30 commits into from
Aug 14, 2020

Conversation

ricea
Copy link
Collaborator

@ricea ricea commented Jul 8, 2020

Make readable, writable, and transform streams transferable via
postMessage(stream, [stream]).

The streams themselves must be transferred, but the chunks written or
read from the streams are cloned, not transferred. Support for
transferring chunks will require API changes and is expected to be added
in a future update.

There is no reference implementation of this functionality as jsdom does
not support transferable objects, and so it wouldn't be testable.

Closes #276.

(See WHATWG Working Mode: Changes for more details.)


Preview | Diff

@ricea
Copy link
Collaborator Author

ricea commented Jul 8, 2020

This is ready for review. @domenic and @MattiasBuelens PTAL.

There's a minor issue that I haven't figured out how to link {{MessagePort}}'s transfer steps. Probably it will require an editorial change to the HTML standard, or maybe it's not worth the effort.

I need to check I don't have any gaps in my tests and port them to wpt, but I don't expect that to take long.

@ricea
Copy link
Collaborator Author

ricea commented Jul 8, 2020

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.

Looks pretty good! 🙂

From your comment on #276, I understand that we will not yet support the "double-transfer" case in this first version. Correct?

There is no reference implementation of this functionality as jsdom does not support transferrable objects, and so it wouldn't be testable.

That is indeed unfortunate.

For remote-web-streams, I wrote a mock implementation of MessageChannel. It doesn't actually do any (de)serialization or send stuff between realms, it's just enough so I can run my tests. But I guess that wouldn't really work for a project like jsdom... 😅

index.bs Outdated Show resolved Hide resolved
1. Perform ! [$WritableStreamDefaultControllerErrorIfNeeded$](|controller|, |value|).
1. If |backpressurePromise| is not undefined,
1. [=Resolve=] |backpressurePromise| with undefined.
1. Set |backpressurePromise| to undefined.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
1. Set |backpressurePromise| to undefined.
1. Set |backpressurePromise| to undefined.
1. Disentangle |port|.

The readable's cancelAlgorithm disentangles its port when it sends the "error" message. Thus, the writable side should do the same when it receives that message.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It's my understanding that we only need to disentangle the port on one side. Although from the implementers' perspective it might be better to explicitly disentangle on both sides. Let me think about it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm leaning towards "no" now, since in platonic spec-land it is redundant to disentangle from both sides, and in practice implementations need to implement MessagePort close() in such a way that both sides get closed after all queued messages have been delivered.

index.bs Outdated Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
@ricea
Copy link
Collaborator Author

ricea commented Jul 8, 2020

From your comment on #276, I understand that we will not yet support the "double-transfer" case in this first version. Correct?

Correct. It's unfortunate, but I said there, it's very complex to support.

That is indeed unfortunate.

For remote-web-streams, I wrote a mock implementation of MessageChannel. It doesn't actually do any (de)serialization or send stuff between realms, it's just enough so I can run my tests. But I guess that wouldn't really work for a project like jsdom... 😅

We could polyfill MessageChannel and MessagePort I suppose, but we'd need quite a high level of fidelity and at that point it might be easier just to implement it in jsdom. Anyway, I'd prefer not to block on that.

Copy link
Member

@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.

Mostly-editorial feedback. It's great to see this coming together!

index.bs Outdated Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
1. Perform ! [$InitializeReadableStream$](|stream|).
1. Let |controller| be a [=new=] {{ReadableStreamDefaultController}}.
1. Let |backpressurePromise| be [=a new promise=].
1. Add a handler for |port|'s {{MessagePort/message}} event with the following steps:
Copy link
Member

Choose a reason for hiding this comment

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

"Add a handler" is not well-defined. I've run into this problem before; you need something like https://wicg.github.io/kv-storage/#add-a-simple-event-listener. Maybe we can upstream that into DOM.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It feels odd having to route the event via JavaScript in the standard when in Blink's implementation we just have blink::NativeEventListener. And it's not like it's some weird edge-case either: it's used in loads of places.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah. To be clear I don't suggest you change anything here, but I'd like to get us better primitives.

index.bs Outdated Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
1. Set |backpressurePromise| to [=a new promise=].
1. Otherwise, if |type| is `"close"`,
1. Perform ! [$ReadableStreamDefaultControllerClose$](|controller|).
1. Disentangle |port|.
Copy link
Member

Choose a reason for hiding this comment

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

Wow, it's weird that this is not a defined term.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, I was confused when I didn't find a definition.

index.bs Outdated Show resolved Hide resolved
ricea added a commit to ricea/web-platform-tests that referenced this pull request Jul 9, 2020
The Streams Standard change whatwg/streams#1053
adds the ability to transfer streams to a different realm using
postMessage(). Add tests for this feature.
@ricea
Copy link
Collaborator Author

ricea commented Jul 9, 2020

I've uploaded the tests at web-platform-tests/wpt#24546. I've left it as a draft PR until we are ready to land this, but please feel free to review anyway.

index.bs Outdated Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
<dfn abstract-op lt="PackAndPostMessage">PackAndPostMessage(|port|, |type|, |value|)</dfn> performs
the following steps:

1. Let |message| be [$OrdinaryObjectCreate$](null).
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
1. Let |message| be [$OrdinaryObjectCreate$](null).
1. Let |message| be ! [$OrdinaryObjectCreate$](null).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

OrdinaryObjectCreate does not return a completion record. This is non-obvious from the definition https://tc39.es/ecma262/2020/#sec-ordinaryobjectcreate but clear from how it is used elsewhere: https://tc39.es/ecma262/2020/#sec-createiterresultobject.

Copy link
Member

Choose a reason for hiding this comment

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

Everything returns a completion record: https://tc39.es/ecma262/#sec-implicit-completion-values

1. Perform ! [$CreateDataProperty$](|message|, "`type`", |type|).
1. Perform ! [$CreateDataProperty$](|message|, "`value`", |value|).
1. Let |targetPort| be the port with which |port| is entangled, if any; otherwise let it be null.
1. Let |options| be «[ "`transfer`" → « » ]».
Copy link
Member

Choose a reason for hiding this comment

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

TODO for me to make these linkable

index.bs Outdated Show resolved Hide resolved
index.bs Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
Copy link
Member

@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.

LGTM; very nice.

We should work on getting a second implementer signoff, especially on the remaining open questions in #276.

1. If ! [$IsReadableStreamLocked$](|readable|) is true, throw a "{{DataCloneError}}"
{{DOMException}}.
1. If ! [$IsWritableStreamLocked$](|writable|) is true, throw a "{{DataCloneError}}"
{{DOMException}}.
Copy link
Member

Choose a reason for hiding this comment

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

Adding these checks here, as opposed to delegating to transferring the readable and writable sides independently, avoids ending up with a "half-transferred" transform stream. That's nice :). I wonder if we should <p class="note"> that? In either case we should be sure we have a test for it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I've updated the tests to test this feature (which is missing from Blink's implementation 😳).

I don't feel I need to call attention to the feature with a note, as it is just a matter of polish.

index.bs Outdated Show resolved Hide resolved
@ricea
Copy link
Collaborator Author

ricea commented Jul 21, 2020

Can I say that Google Chrome is interested even though I'm the one who wrote the change? 😄

@jorendorff and @youennf could I hear your views on this?

@domenic
Copy link
Member

domenic commented Jul 21, 2020

Yes, you certainly can :)

@ricea
Copy link
Collaborator Author

ricea commented Jul 21, 2020

Google Chrome is interested!

@ricea
Copy link
Collaborator Author

ricea commented Aug 13, 2020

@MattiasBuelens Github thinks I have unresolved comments from you, but I can't tell which they are. Are you okay with landing this, or do you think it needs more work?

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.

LGTM. 👍

Perhaps it's because this review comment has a "suggested change" in it that wasn't committed? Anyway, you can ignore that.

ricea and others added 10 commits August 14, 2020 08:40
Make readable, writable, and transform streams transferable via
postMessage(stream, [stream]).

The streams themselves must be transferred, but the chunks written or
read from the streams are cloned, not transferred. Support for
transferring chunks will require API changes and is expected to be added
in a future update.

There is no reference implementation of this functionality as jsdom does
not support transferrable objects, and so it wouldn't be testable.

Closes whatwg#276.
Co-authored-by: Mattias Buelens <[email protected]>
Co-authored-by: Domenic Denicola <[email protected]>
@ricea ricea merged commit 1fd19a0 into whatwg:master Aug 14, 2020
@ricea ricea deleted the transferable-streams branch August 14, 2020 02:11
ricea added a commit to web-platform-tests/wpt that referenced this pull request Oct 7, 2020
The Streams Standard change whatwg/streams#1053
adds the ability to transfer streams to a different realm using
postMessage(). Add tests for this feature.
moz-v2v-gh pushed a commit to mozilla/gecko-dev that referenced this pull request Oct 9, 2020
…stonly

Automatic update from web-platform-tests
Add tests for transferable streams (#24546)

The Streams Standard change whatwg/streams#1053
adds the ability to transfer streams to a different realm using
postMessage(). Add tests for this feature.

--

wpt-commits: f2eb8b9fa0d0d11429ce4952dbb9dd796c18e0da
wpt-pr: 24546
sidvishnoi pushed a commit to sidvishnoi/gecko-webmonetization that referenced this pull request Oct 13, 2020
…stonly

Automatic update from web-platform-tests
Add tests for transferable streams (#24546)

The Streams Standard change whatwg/streams#1053
adds the ability to transfer streams to a different realm using
postMessage(). Add tests for this feature.

--

wpt-commits: f2eb8b9fa0d0d11429ce4952dbb9dd796c18e0da
wpt-pr: 24546
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

can streams be transferred via postMessage()?
3 participants