Created
December 13, 2012 07:14
-
-
Save evantahler/4274698 to your computer and use it in GitHub Desktop.
Help with node.js' handling of domain exceptions from clients created outside of their scopes. Check the comment for description
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var domain = require('domain'); | |
var redis = require('redis'); | |
var eventEmitter = require('events').EventEmitter; | |
var tests = []; | |
var testCounter = 0; | |
var runTest = function(){ | |
if(tests.length > testCounter){ | |
tests[testCounter](); | |
}else{ | |
console.log("all done!") | |
process.exit(); | |
} | |
} | |
var myDomain = new domain.create(); | |
myDomain.on('error', function(err){ | |
console.log('Yo, I just saved you from the error: ' + err); | |
testCounter++; | |
runTest(); | |
}); | |
// PASSING | |
tests[0] = function(){ | |
myDomain.run(function(){ | |
throw new Error('A simple error'); | |
}); | |
}; | |
// PASSING | |
tests[1] = function(){ | |
myDomain.run(function(){ | |
setTimeout(function(){ | |
process.nextTick(function(){ | |
var E = new eventEmitter; | |
E.on('thing', function(){ | |
throw new Error('A deeply nested error'); | |
}) | |
setTimeout(function(){ | |
E.emit('thing'); | |
}, 100) | |
}); | |
}, 100); | |
}); | |
} | |
// PASSING | |
var Emm = new eventEmitter; | |
Emm.on('thing', function(){ | |
throw new Error('Emmited Error defined outside of scope'); | |
}) | |
tests[2] = function(){ | |
myDomain.run(function(){ | |
setTimeout(function(){ | |
Emm.emit('thing'); | |
}, 100) | |
}) | |
}; | |
// PASSING | |
tests[3] = function(){ | |
myDomain.run(function(){ | |
clientA = redis.createClient(); | |
clientA.hget('hash', 'key', function(err, data){ | |
throw new Error('An error after redis (A)'); | |
}); | |
}); | |
} | |
// PASSING | |
tests[4] = function(){ | |
clientB = redis.createClient(); | |
myDomain.run(function(){ | |
clientB.hget('hash', 'key', function(err, data){ | |
throw new Error('An error after redis (B)'); | |
}); | |
}); | |
}; | |
// FAILING | |
clientC = redis.createClient(); | |
tests[5] = function(){ | |
myDomain.run(function(){ | |
clientC.hget('hash', 'key', function(err, data){ | |
throw new Error('An error after redis (C)'); | |
}); | |
}); | |
}; | |
// start it up | |
runTest(); |
Gist needs a mobile view...
Thanks for all your help. It's all starting to make sense now :D
Thanks again @othiym23
ActionHero's cache commands now don't crash the server if something goes wrong :D
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
You're right; I was looking at all of this on my iPhone's screen and didn't notice how tests #4 and #5 actually differed. Sorry! Pretty good theory I pulled straight out of my ass, though, no? ;)
The reason #5 is failing is pretty straightforward – you're creating the client in a completely different execution context than the one in which the call to Redis is being made. This is exactly the issue I was talking about in my first comment – by the time the domain comes into play, the EventEmitter underlying the connection is already in existence, and therefore doesn't get created / implicitly bound to the domain. Test #2 passes because the EventEmitter gets picked up because you're using it directly in domain.run(); in #5 the driver itself is an EE, but so is the underlying socket, so the domain is making it part of the way there but not all the way down through the layers of wrapping.
If you know that each connection is going to be bound to one domain for the lifetime of an app, you can use the the domain.add() call to bind the underlying EventEmitter to the domain. However, most of the time that isn't what you want, because different calls out to Redis are going to be attached to different domains. If you look at https://github.com/mranney/node_redis/pull/310/files you'll see how small the necessary change to a connection-pooling module is.