Skip to content

Commit

Permalink
Fix context middleware to preserve domains
Browse files Browse the repository at this point in the history
When executing a request using a pooled connection, connectors
like MongoDB and/or MySQL rebind callbacks to the domain which
issued the request, as opposed to the domain which opened the pooled
connection.

This commit fixes the context middleware to play nicely with that
mechanism and preserve domain rebinds.
  • Loading branch information
projectxmaker authored and Miroslav Bajtoš committed Jan 7, 2015
1 parent d77c5fa commit ca0208d
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 6 deletions.
26 changes: 20 additions & 6 deletions server/middleware/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ var loopback = require('../../lib/loopback');
var juggler = require('loopback-datasource-juggler');
var remoting = require('strong-remoting');
var cls = require('continuation-local-storage');
var domain = require('domain');

module.exports = context;

Expand Down Expand Up @@ -44,6 +45,13 @@ function context(options) {
var scope = options.name || name;
var enableHttpContext = options.enableHttpContext || false;
var ns = createContext(scope);

var currentDomain = process.domain = domain.create();
currentDomain.oldBind = currentDomain.bind;
currentDomain.bind = function(callback, context) {
return currentDomain.oldBind(ns.bind(callback, context), context);
};

// Return the middleware
return function contextHandler(req, res, next) {
if (req.loopbackContext) {
Expand All @@ -53,13 +61,19 @@ function context(options) {
// Bind req/res event emitters to the given namespace
ns.bindEmitter(req);
ns.bindEmitter(res);

currentDomain.add(req);
currentDomain.add(res);

// Create namespace for the request context
ns.run(function processRequestInContext(context) {
// Run the code in the context of the namespace
if (enableHttpContext) {
ns.set('http', {req: req, res: res}); // Set up the transport context
}
next();
currentDomain.run(function() {
ns.run(function processRequestInContext(context) {
// Run the code in the context of the namespace
if (enableHttpContext) {
ns.set('http', {req: req, res: res}); // Set up the transport context
}
next();
});
});
};
}
Expand Down
71 changes: 71 additions & 0 deletions test/loopback.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
var it = require('./util/it');
var describe = require('./util/describe');
var Domain = require('domain');
var EventEmitter = require('events').EventEmitter;

describe('loopback', function() {
var nameCounter = 0;
Expand Down Expand Up @@ -388,4 +391,72 @@ describe('loopback', function() {
});
});
});

describe.onServer('loopback.getCurrentContext', function() {
var runInOtherDomain;
var runnerInterval;

before(function setupRunInOtherDomain() {
var emitterInOtherDomain = new EventEmitter();
Domain.create().add(emitterInOtherDomain);

runInOtherDomain = function(fn) {
emitterInOtherDomain.once('run', fn);
};

runnerInterval = setInterval(function() {
emitterInOtherDomain.emit('run');
}, 10);
});

after(function tearDownRunInOtherDomain() {
clearInterval(runnerInterval);
});

// See the following two items for more details:
// https://github.com/strongloop/loopback/issues/809
// https://github.com/strongloop/loopback/pull/337#issuecomment-61680577
it('preserves callback domain', function(done) {
var app = loopback();
app.use(loopback.rest());
app.dataSource('db', { connector: 'memory' });

var TestModel = loopback.createModel({ name: 'TestModel' });
app.model(TestModel, { dataSource: 'db', public: true });

// function for remote method
TestModel.test = function(inst, cb) {
var tmpCtx = loopback.getCurrentContext();
if (tmpCtx) tmpCtx.set('data', 'a value stored in context');
if (process.domain) cb = process.domain.bind(cb); // IMPORTANT
runInOtherDomain(cb);
};

// remote method
TestModel.remoteMethod('test', {
accepts: { arg: 'inst', type: uniqueModelName },
returns: { root: true },
http: { path: '/test', verb: 'get' }
});

// after remote hook
TestModel.afterRemote('**', function(ctxx, inst, next) {
var tmpCtx = loopback.getCurrentContext();
if (tmpCtx) {
ctxx.result.data = tmpCtx.get('data');
}else {
ctxx.result.data = 'context not available';
}
next();
});

request(app)
.get('/TestModels/test')
.end(function(err, res) {
if (err) return done(err);
expect(res.body.data).to.equal('a value stored in context');
done();
});
});
});
});

0 comments on commit ca0208d

Please sign in to comment.