With much, much help from Patrick McManus, I think I finally understand what Firefox SPDY sandboxes are doing with respect to SPDY version 3 flow control. I also have a better understanding of Chrome flow control thanks to this exercise. I even think I have a solution to node-spdy's woes holding a spdy/3 conversation with Firefox.
At the outset of a SPDY conversation, Firefox specifies an initial receive window of 256mb for flow control. That is much higher than the 64kb default in the specification. I remain skeptical that Firefox's large window does little more than disable flow control, but it is definitely throwing real world stuff at node-spdy, so I must support it.
As I found last night, Firefox is sending additional WINDOW_UPDATE frames after node-spdy has sent the DATA FIN packet. Currently node-spdy handles this situation by closing the stream, forcing Firefox to open a new SPDY session to require any remaining resources (but giving up on in-transit resources).
Instead, I add code to RST_STREAM in response to a WINDOW_UPDATE on a closed stream. More specifically, I mark the stream as invalid (0x02):
function Connection(socket, pool, options) {
// ...
this.parser.on('frame', function (frame) {
// ...
if (frame.type === 'SYN_STREAM') { /* ... */ }
else {
if (frame.id) {
// Load created one
stream = self.streams[frame.id];
// Fail if not found
if (stream === undefined) {
if (frame.type === 'RST_STREAM') return;
console.log("frame not found: ", frame);
//return self.emit('error', 'Stream ' + frame.id + ' not found');
self.write(self.framer.rstFrame(frame.id, 2));
return;
}
}
// ...
}
});
// ...
}Now, when I point my sandbox version of Firefox at my nody-spdy site, I finally see the complete web page with no broken images:Hooray!
The debugging code that I had added to node-spdy shows that Firefox in fact sends 26 WINDOW_UPDATE frames for the two large resources on that page:
➜ express-spdy-test node app
Express server listening on port 3000 in development mode
settings.initial_window_size: 268435456
...
sending 7: 0 (false)
[secureConnection] finish
sending 7: 0 (true)
sending 9: 0 (false)
[secureConnection] finish
sending 9: 0 (true)
frame not found: { type: 'WINDOW_UPDATE', id: 7, delta: 65660 }
frame not found: { type: 'WINDOW_UPDATE', id: 7, delta: 66320 }
frame not found: { type: 'WINDOW_UPDATE', id: 7, delta: 65660 }
frame not found: { type: 'WINDOW_UPDATE', id: 9, delta: 65660 }
frame not found: { type: 'WINDOW_UPDATE', id: 9, delta: 66320 }
frame not found: { type: 'WINDOW_UPDATE', id: 9, delta: 65660 }
frame not found: { type: 'WINDOW_UPDATE', id: 7, delta: 66320 }
frame not found: { type: 'WINDOW_UPDATE', id: 9, delta: 66320 }
frame not found: { type: 'WINDOW_UPDATE', id: 7, delta: 66320 }
frame not found: { type: 'WINDOW_UPDATE', id: 9, delta: 66320 }
frame not found: { type: 'WINDOW_UPDATE', id: 7, delta: 65660 }
frame not found: { type: 'WINDOW_UPDATE', id: 9, delta: 65660 }
frame not found: { type: 'WINDOW_UPDATE', id: 7, delta: 66320 }
frame not found: { type: 'WINDOW_UPDATE', id: 9, delta: 66320 }
frame not found: { type: 'WINDOW_UPDATE', id: 7, delta: 65660 }
frame not found: { type: 'WINDOW_UPDATE', id: 9, delta: 65660 }
frame not found: { type: 'WINDOW_UPDATE', id: 7, delta: 66320 }
frame not found: { type: 'WINDOW_UPDATE', id: 9, delta: 66320 }
frame not found: { type: 'WINDOW_UPDATE', id: 7, delta: 66320 }
frame not found: { type: 'WINDOW_UPDATE', id: 9, delta: 66320 }
frame not found: { type: 'WINDOW_UPDATE', id: 7, delta: 65660 }
frame not found: { type: 'WINDOW_UPDATE', id: 9, delta: 65660 }
frame not found: { type: 'WINDOW_UPDATE', id: 7, delta: 66320 }
frame not found: { type: 'WINDOW_UPDATE', id: 9, delta: 66320 }
frame not found: { type: 'WINDOW_UPDATE', id: 9, delta: 65660 }
frame not found: { type: 'WINDOW_UPDATE', id: 9, delta: 66320 }
...Looking through Firefox's HTTP log, it seems that Firefox is sending those WINDOW_UPDATE frames as it receives DATA frames. Where node-spdy sees the WINDOW_UPDATE frames after the DATA FIN, Firefox sees them before.I have the feeling that my simple flow control buffer in node-spdy may be too simple. I will investigate that another night. Before calling it a night tonight, I make the localhost network interface (
lo) a little more realistic with traffic control:sudo tc qdisc add dev lo root netem delay 50msThe hope is that perhaps the unrealistic testing on the loopback interface is somehow producing these weird results.
It is not.
I see the exact same number of WINDOW_UPDATE frames from Firefox even with a more realistic RTT in effect. Ah well, even eliminating the obvious solution is helpful sometimes.
I will pick back up tomorrow investigating the apparent discrepancy between when node-spdy sends DATA frames and when Firefox receives them.
UPDATE: I have updated the code on https://test.spdybook.com:3000/ to use this recent version of node-spdy in case anyone is interested in trying it. The certificate for that site is invalid, but is pulled from node-spdy if you want to decrypt the packets: https://github.com/indutny/node-spdy/tree/master/keys.
Day #387

No comments:
Post a Comment