DataChannel API and onopen

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?


-- 
Randell Jesup
randell-ietf@jesup.org

Received on Monday, 23 April 2012 16:02:20 UTC