Re: DataChannel API and onopen

On 2012-04-23 18:01, Randell Jesup wrote:
> In going over the details for the DataChannel minor protocol and how it
> works over SCTP, we realized we'll need a 3-way handshake instead of a
> 2-way.  The reason is that an OPEN_RESPONSE sent ordered followed by a
> data message sent unordered would allow the data message to come in at
> the other end before the open response, and since we don't know the
> input-stream ->  output-stream mapping until we get the OPEN_RESPONSE, we
> might not know what to do with it.  So we plan to add an ACK message to
> the protocol to provide the 3-way handshake.
>
> We now have to decide what to do about the JS-level API; which to recap
> currently requires waiting for onopen on the opener's side, and allows
> the answerer to send() immediately on getting ondatachannel.
>
> We've figured out that we can allow send() before onopen (in either
> direction) by forcing data to be in-order until the 3-way handshake is
> complete.  Obviously, if you're using in-order (reliable or unreliable)
> this doesn't matter.  It does cause and data sent to be buffered in the
> stack(s) until it can be delivered, which typically would be no real
> problem unless the OPEN (or OPEN_RESPONSE) was lost and had to be
> re-transmitted.
>
> There are two really "simple and clean" solutions:
> 1) both sides wait for onopen()
> 2) neither side waits for onopen()
> Note that in #2 we'd still have onopen() for ease of porting WebSockets
> code to it DataChannels.
>
> We could also keep the current API (onopen at the opener, immediate on
> the receiver).
>
> #1 requires the application maintain state in a number of cases where it
> would not otherwise need to, or buffer within the app.  I.e. an
> application that wants to send data on a channel not yet open must open
> it and somehow defer the sending until it's open.  This can be
> encapsulated in JS functions:
>
>       temp = peer.createDataChannel(..)
>       temp.data_to_send = "I want to send just this";
>       temp.onopen = function() {
>            temp.send(temp.data_to_send);
>            temp.close();  // don't wait for GC to close/reclaim stream!
>            temp = null; // available for GC
>            };
>
> Of course, not as straightforward as:
>      temp = peer.createDataChannel(..)
>      temp.send("I want to send just this");
>      temp.close();
>      temp = null;
>
> but not a lot worse.  It's more complex if you might want to send more
> data to the channel and need to buffer all of it in the app (think a log
> channel) while waiting for it to open.  Commonly the answerer will wait
> for incoming data before sending anything, but not in all cases.
>
> It seems to me that forcing in-order delivery until the handshake is
> complete is simpler for the user, and only rarely has any impact on the
> application.  The exception might be if you wanted to dump a bunch of
> sends that were reliable and out-of-order immediately on creation of the
> channel - and I don't see it being a major issue there - and you can
> still wait on onopen if you want.
>
> Recap: #2 is slightly closer to how WebSockets works (though anything
> coded for WebSockets' onopen behavior would still work in #1), and #2
> avoids potential additional buffering in the stack if the
> OPEN/OPEN_RESPONSE is lost.  (If it's not lost, there's really no extra
> buffering occurring, except maybe in the rare case where we need to
> increase the maximum number of simultaneous streams.)
>
> Opinions?

In approach #2, we move away from behaving like a WebSocket. More 
precisely, a DataChannel could still be used instead of a WebSocket but 
a WebSocket couldn't replace a DataChannel since a call to send() before 
the "open" event would result in a InvalidStateError.

I've drawn some simple signaling diagrams below to help myself to 
overview this. Let me know if they are over-simplified or perhaps even 
wrong.

What we currently have with a 2-way handshake with comes with some problems.

2-way handshake
A -- OPEN      -> B (datachannel@B)
A <- OPEN_RESP -- B (open@A)

Considering a 3-way handshake, approaches #1 and #2 would basically fire 
the "open" events at the same time, but the difference would be that in 
#1 it wouldn't be OK to call send() before the "open" event.

3-way handshake
A -- OPEN      -> B (datachannel)@B)
A <- OPEN_RESP -- B (open@A)
A -- ACK       -> B (open@B)

Couldn't we keep our current API behavior by delaying the "datachannel" 
event on the B-side as described below? The order that the events are 
trigger are reversed compared to the 2-way handshake above, but is that 
a problem?

3-way handshake
A -- OPEN      -> B
A <- OPEN_RESP -- B (open@A)
A -- ACK       -> B (datachannel@B)

/Adam

Received on Tuesday, 24 April 2012 14:24:10 UTC