Thursday, June 9, 2011

SPDY Parser Destruction

‹prev | My Chain | next›

More or less satisfied with the SPDY server push implementation in node-spdy, I switch back to some errors that I was seeing the other day:
➜  node-spdy git:(reply-compression) ✗ node ./test/spdy-server.js
TLS NPN Server is running on port : 8081

node.js:183
throw e; // process.nextTick error, or 'error' event on first tick
^
TypeError: Object [object Object] has no method 'destroy'
at CleartextStream.onclose (stream.js:92:10)
at CleartextStream.emit (events.js:81:20)
at Array. (tls.js:654:22)
at EventEmitter._tickCallback (node.js:175:26)
I spent some time investigating the SSL negotiation that caused this the other night. Tonight I would like to see what is going on inside the code.

Working up, through the stacktrace, I start with tls.js:654, which is a destroy of the secure pair:

SecurePair.prototype.destroy = function() {
var self = this;

if (!this._doneFlag) {
this._doneFlag = true;
//..

process.nextTick(function() {
self.encrypted.emit('close');
self.cleartext.emit('close');
});
}
};
The process.nextTick() is just a node equivalent of setTimeout(fn, 0). It does have the effect of disassociating the event from the code. The stacktrace that I get indicates that the error is just one of many callbacks being invoked at the nextTick of the reactor loop.

Given what I saw the other night, it looks as though the renegotiation of the SSL handshake is causing the secure pair of streams in node to be destroyed and rebuilt multiple times. The end result being the crash with stacktrace. My solution the other night was to punt. Specifically, I added a process.on handler:
// Don't crash on errors
process.on('uncaughtException', function (err) {
console.log('Caught uncaughtException: ' + err.stack);
});
To come up with a better solution, I need to understand the rest of the stacktrace. At the top, stream.js:92 is, not surprisingly, a destroy() call:
  function onclose() {
//...
dest.destroy();
}
But where does that dest object get defined? It is not passed into onclose. Rather, it is passed into the pipe() method:
Stream.prototype.pipe = function(dest, options) {
//...
function onclose() {
//...
dest.destroy();
}
}
Ah, that then, is the explanation. The node-spdy parser is piped to the connecton stream in spdy/core:
var Server = core.Server = function(options, requestListener) {
//...
tls.Server.call(this, options, function(c) {
//...
var parser = createParser(c.zlib)

c.pipe(parser);
//...
I could not quite bring myself to add a destroy() to the Parser class the other night without understanding why I was doing so. Now I know. If you are going to pipe() stuff to a thing, that thing simply must support it.

So I add a NOP destroy() function to Parser and call it a night:
/**
* End of stream
*/
Parser.prototype.end = function() {};

Parser.prototype.destroy = function() {};



Day #45

No comments:

Post a Comment