Gareth Charnock Gareth Charnock - 26 days ago 9
Node.js Question

Using a domain to test for an error thrown deep in the call stack in node.

I'm trying to write some tests that catch errors that are potentially thrown deep in a nest of callbacks and I decided to try using domains for this. I've managed to simplify to the following test case:

"use strict";
var assert = require("assert");
var domain = require("domain");

function wrapInDomain(throwsAnError, callback) {
var thisDomain = domain.create();

thisDomain.on("error", function(error) {
console.log("calling callback");
//thisDomain.destory(); // I'm not sure if I should do this
callback(error);
});

thisDomain.run(function() {
process.nextTick(function(){
throwsAnError();
});
});
}

function throwsAnError() {
process.nextTick(function(){
throw new Error("I sensed a great disturbance in the force, as if millions of voices cried out in terror and were suddenly silenced. I fear something terrible has happened.");
});
}

describe("describe something that throws an error", function(){

it("it clause", function(done) {
wrapInDomain(throwsAnError, function(theError) {
console.log("got an error " + theError);
assert(false); //Assert something
done();
});
});


});


If the assert passes mocha gives the nice colourful summary of how many tests passed or failed. But if an assert fails, node seems to crash and drops directly. The error listed is the original rather than the failed assert.

Am I doing something very wrong here? Or is this some kind of bug that needs reporting?

calling callback
got an error Error: I sensed a great disturbance in the force, as if millions of voices cried out in terror and were suddenly silenced. I fear something terrible has happened.

/home/gareth2/cloud/apifacade/src/test/resources/testCase.js:23
throw new Error("I sensed a great disturbance in the force, as if mill
^
Error: I sensed a great disturbance in the force, as if millions of voices cried out in terror and were suddenly silenced. I fear something terrible has happened.
at /home/gareth2/cloud/apifacade/src/test/resources/testCase.js:23:15
at process._tickDomainCallback (node.js:463:13)

*node crashes*


I'm using the following version of node.

$ node --version
v0.10.33

Answer Source

The problem is that you need to exit the domain before Mocha can trap the failing exception, because event handlers that are bound to a domain execute in the domain. If you change your error handler to this, it will work:

thisDomain.on("error", function(error) {
    thisDomain.exit();
    process.nextTick(function () {
        callback(error);
        console.log("calling callback");
    });
});

You need to call process.nextTick (or setImmediate or setTimeout(..., 0)) in the code above so that the statement callback(error) executes in the domain that becomes effective after thisDomain.exit() runs. The way Node works, when you create a new callback for handling an event or to run with process.nextTick (or equivalent functions), then the callback as a whole is associated with a specific domain. Suppose the following callback

myDomain.on("error", function () {
  A;
  myDomain.exit();
  B;
});

Any error caused by A will be handled in myDomain, but the same is true of B because B belongs to the same callback as A.

By using process.nextTick, we create a new callback which is associated with the new domain that takes effect after thisDomain.exit() executes. Modifying the example above, it would be:

myDomain.on("error", function () {
  A;
  myDomain.exit();
  process.nextTick(function () {
    B;
  });
});

Now B belongs to a different callback from A and this callback was created after we exited myDomain so it executes in the domain that was in effect before myDomain was created.