Sunday, May 6, 2012

Adjustable Flow Control in Node-Spdy

‹prev | My Chain | next›

After an unexpected detour last night to add support for parsing SETTINGS frames, I hope to add adjustable data transfer windows to node-spdy today.

The ability to adjust the data transfer window size, and indeed the concept of a data transfer window, is new in version 3 of the SPDY specification. This allows the recipient of a SPDY stream to tell the sender to throttle data transfer. The ability for the recipient to throttle data transfer can be all kinds of useful if the recipient is processing data at much slower rates than the sender is capable of sending it. Throttling can also be useful if the recipient is attempting to optimize how it processes data.

Whatever the reason, I have many of the low-level tools needed to support data transfer windows in the spdy-v3 branch of node-spdy. Now it is time to add some higher level support. Specifically, the internal data transfer window is calculated and managed per-SPDY stream, but the default transfer window is stored on the connection. So first, I move the default value for the window into the Connection class and have the Stream class set its internal values based on the Connection object:
function Connection(socket, pool, options) {
  // ...
  // Data transfer window defaults to 64kb
  this.transferWindowSize = Math.pow(2,16);
  // ...
};

function Stream(connection, frame) {
  // ...
  this._transferWindowSize = connection.transferWindowSize;
  this._initialTransferWindowSize = connection.transferWindowSize;
  // ...
};
The _transferWindowSize will be constantly adjusted as the server sends data back to the client. I need to store the _initialTransferWindowSize to aid when the client wants to adjust the data transfer window.

Speaking of adjusting the data transfer window, when SETTINGS frames come in, I am calling the setDefaultTransferWindow() method. Yesterday, that was a tracer bullet method, but tonight, I update the connection's transfer window and tell each active stream to do the same:
//
// ### function setDefaultTransferWindow (settings)
// #### @settings {Object}
// Update the default transfer window -- in the connection and in the
// active streams
//
Connection.prototype.setDefaultTransferWindow = function(settings) {
  if (settings.initial_window_size) {
    this.transferWindowSize = settings.initial_window_size;

    for (var streamID in this.streams) {
      this.streams[streamID].updateTransferWindowSize(settings.initial_window_size);
    }
  }
};
As for the updateTransferWindowSize() method on Stream, this is simple enough:
//
// ### function updateTransferWindowSize (size)
// #### @size {Integer}
// Update the internal data transfer window
//
Stream.prototype.updateTransferWindowSize = function updateTransferWindowSize(size) {
  var diff = size - this._initialTransferWindowSize;

  this._transferWindowSize += diff;
  this._initialTransferWindowSize = size;
};
With that, I am ready to test things out. Since the only client that I have is Chome, I have to somehow get it to adjust the data transfer window via a SETTINGS frame. I think this is possible, but a bit of a pain. I send out a SETTINGS frame from the server that persists a 16kb data transfer window (the default is 64kb):
// ...
    settings.writeUInt32BE(0x01000007, 20, true); // Entry ID and Persist flag
    settings.writeUInt32BE(Math.pow(2, 14) & 0x7fffffff, 24, true); // Window Size (KB)
// ...
Since the persist flag is set, if I load the page, stop the server, then load the page again, the browser retains the lower-than-default transfer size:
SPDY_SESSION_UPDATE_STREAMS_SEND_WINDOW_SIZE
--> delta_window_size = -49152
SPDY_SESSION_SEND_SETTINGS
--> settings = ["[id:4 flags:2 value:100]","[id:7 flags:2 value:16384]"]
At least the browser seems to retain the lower-than-default setting. In reality, the server responds with 16kb worth of the very large images and waits for the browser to issue a WINDOW_UPDATE:


But that WINDOW_UPDATE never comes. Eventually, the session just times out.

If I remove the new connection adjustment code—in other words, I ignore the 16kb persisted data transfer window—then the web page and large images load as desired. It seems that, despite the opening SETTINGS frame that includes the 16kb window, Chrome is behaving as if the transfer window had not been adjusted at all from the default.

Aw, man! In fact that is just what is happening. When the server sends out a different-than-default transfer window, it is a transfer window for the server as a recipient. I am not telling the client that its transfer window should be 16kb—only the client can change its own transfer window. In reality I am telling the browser that, if it needs to send me-the-server any data (e.g. in a POST), then it should use this non-default data transfer window.

Dang it. I think my adjustable window code is sound, but I cannot be sure without a client to test it. I will have to think about this....

Any suggestions?


Day #378

No comments:

Post a Comment