Re: [whatwg/streams] A guide for a bidirectional transport? (#1032)

Here's what I did for WebSocketStream. I'm not saying this is necessarily correct, in fact I welcome feedback on what the correct behaviour should be.

> What signals need to be propagated to source and sink when...
> 
> * a bidirectional transport closes normally,

Source is closed if not already closed. Sink is errored if not already closed.

> * a bidirectional transport closes unexpectedly,

Source and sink are errored if not already closed.

> * a bidirectional transport errors (I think this is very similar to _closing unexpectedly_ but I wanted to mention it nonetheless since WebSocket has a separate event for that).

WebSocket exposing "error" as distinct from "close" is a weird property of the JavaScript API. At least in Chrome's implementation "closing unexpectedly" leads to an "error" event followed by "close" with `wasClean` set to false, whereas other cases where there may have been data loss only have the "close" event with `wasClean` set to false. WebSocketStream treats all kinds of unclean closes the same.

I would expect almost all bidirectional transports to treat errors and unexpected closes the same way.

> What signal needs to be propagated to the bidirectional transport (and thus potentially implicitly to the sink) when...
> 
> * the source is being cancelled (and what do we need to wait for in that method).

In WebSocketStream, I treat this as a close with unspecified code (unless cancel is called like `cancel({code: 4000})`).

It doesn't wait for anything. The reasoning behind this is that calling cancel() indicates loss-of-interest in reading anything else from the stream. If we have lost interest then naturally we don't care about whether it was closed cleanly or how long it took.

The sink will be errored when the closing handshake completes.

> What signal needs to be propagated to the bidirectional transport (and thus potentially implicitly to the source) when...
> 
> * the sink is being closed (and what do we need to wait for in that method),

In WebSocketStream, I treat this as close with unspecified code. It waits for the closing handshake to complete, and resolves or rejects based on whether the close was clean.

> * the sink is being aborted (and what do we need to wait for in that method).

I treat this as close and don't wait. As with cancel, a return code can be specified like `abort({code: 4000})`.

> Does this cover all cases?

WebSocketStream and QuicTransport have additional non-stream APIs to close the connection. In the case of WebSocketStream `close()`, it behaves just like a server-initiated close and nothing happens to the streams until the closing handshake succeeds or fails.

C++ APIs also need to worry about what happens if they are running in a frame which is then detached. In WebSocketStream the connection is closed but the streams are not notified in any way.

> For wrapping an event-based WebSocket, these are my guesses and **please correct me if I'm wrong**:

> * a bidirectional transport closes normally: _Close the source's controller if not already closed (I've wrapped it in a `try/catch` - please tell me if this is correct). Error the sink's controller._

Correct.

> Is this correct and does this cover all potential side effects?

Yes, except that in my opinion source`cancel()` should not wait.

I'm going to start implementing [BidirectionalStreams](https://wicg.github.io/web-transport/#bidirectionalstream) for QuicTransport very soon. I may have more insights once I have done that.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/whatwg/streams/issues/1032#issuecomment-603393960

Received on Tuesday, 24 March 2020 17:34:12 UTC