baryshev/connect-domain

fails when i use connect-mongodb to store the session

Opened this issue · 4 comments

This is a so convoluted error that took me a while to discover.. It is an specific combination of modules (request and connect-mongodb).

Here is the smallest version I did:

//npm install express request mongodb connect-domain connect-mongodb

var request = require('request'),
    express = require('express'),
    connectDomain = require('connect-domain'),
    http = require('http');

var MongoStore = require('connect-mongodb'), 
    Db = require('mongodb').Db, 
    Server = require('mongodb').Server, 
    server_config = new Server('localhost', 27017, {auto_reconnect: true, native_parser: true}), 
    db = new Db('test', server_config, {}),
    mongoStore = new MongoStore({db: db});

var app = express();

app.configure(function(){
  //if i move this line after express.session 
  //everything works as expected.
  this.use(connectDomain());

  this.use(express.cookieParser());
  this.use(express.session({secret: 'foobar', store: mongoStore }));

  this.use(app.router);

  this.use(function(err, req, res, next){
    res.end(err.message);
  });
});

app.get('/', function(req, res, next){
  var r = request.get('http://localhost:9999');
  //at this point r.domain is null.

  //If i explicitly add this stream to the domain it works
  //Slightly changing connect-domain to store the domain in req, if i do 
  //req.__domain.add(r); it works
  r.pipe(res);
});

http.createServer(app).listen(3000, function(){
  console.log('listening on http://localhost:3000');
});

I have the exact same problem when using Redis as a session store.

I had the same issue and upon further inspection it appears that adding a process.nextTick to RedisStore gets it working if you use the domain middleware first.

The MemoryStore session store for express includes the process.nextTick, but RedisStore didn't. When I add the session middleware, if I do it like this, it will work with domains:

var sessionFn = express.session({ store: new RedisStore(redisOptions),
  cookie: {httpOnly: true, maxAge: 7 * 24 * 60 * 60 * 1000},
  secret: ****"'})
 app.use(function (req, res, next) {
  process.nextTick(function() {
    sessionFn(req, res, next)
  })
})

I'm not sure why, I guess it has something to do with how domains work though.

The problem is, this module calls dispose()s too early, which causes callbacks to silently cancel...

As a solution, I've wrapped dispose in process.nextTick, and I'm not using this module, as it's under 20 lines:

    var domain = require('domain');
    ...
    app.use(function(req, res, next) {
      var d = domain.create();

      function dispose() {
        //let dust settle
        process.nextTick(d.dispose.bind(d));
      }

      res.on('close', dispose);
      res.on('finish', dispose);

      d.on('error', function (err) {
        dispose();
        next(err);
      });

      d.run(next);
    });

Will create a new module soon...

Beware, asynchronous jobs related to the request will get closed after a tick, so if they last longer than that, they will get silently cancelled. This is good and bad, good because it clears up callbacks related to a crash, though bad if you're wanting to do something in after the request, like logging.