2

I am trying to get my MEAN application ready for production. The application was built on the Mean.js boilerplate. From my understanding, MEAN.js uses Forever.js to restart the application after an error (although documentation on preparing Mean.js for production is severely lacking); however, it appears the suggested way to handle the application crashing is using Node's Domains in conjunction with clusters. Here are a few references:

  • This is from Node's webpage on the deprecated uncaughtException Event:

Note that uncaughtException is a very crude mechanism for exception handling.

Don't use it, use domains instead.

Although I have found many suggestions for using domains, I have yet to find one that tells me what needs to be done to incorporate domains in an application, especially one that has already been developed.

The Questions

  • What do I need to do to integrate node domains into a Mean.js application? From what I have gathered (from the Node.js domains webpage and here), you would go into server.js in the root of the Mean.js project and do something similar to this:

    var cluster = require('cluster');
    var PORT = +process.env.PORT || 1337;
    
    if (cluster.isMaster) {
      //Fork the master as many times as required.
      cluster.fork();
      cluster.fork();
    
      cluster.on('disconnect', function(worker) {
        console.error('disconnect!');
        cluster.fork();
      });
    
    } else {    
      var domain = require('domain');
      var d = domain.create();
      d.on('error', function(er) {
        console.error('error', er.stack);
    
        try {
          // make sure we close down within 30 seconds
          var killtimer = setTimeout(function() {
            process.exit(1);
          }, 30000);
          // But don't keep the process open just for that!
          killtimer.unref();
    
          // stop taking new requests.
          server.close();
    
          // Let the master know we're dead.  This will trigger a
          // 'disconnect' in the cluster master, and then it will fork
          // a new worker.
          cluster.worker.disconnect();
    
          // try to send an error to the request that triggered the problem
          res.statusCode = 500;
          res.setHeader('content-type', 'text/plain');
          res.end('Oops, there was a problem!\n');
        } catch (er2) {
          // oh well, not much we can do at this point.
          console.error('Error sending 500!', er2.stack);
        }
      });
    
      d.run(function() {
        //Place the current contents of server.js here.
      });
    }
    
  • Do I need to wrap all of the backend controllers in domain.run()?

Community
  • 1
  • 1
c1moore
  • 1,827
  • 17
  • 27

1 Answers1

1

This answer was found by experimenting and a lot more digging. I had to edit both server.js and config/express.js to use domains. The domain is added part of the Express middleware for each request. Do not use the code in the question, it won't work as is.

First, the changes I made to server.js:

var init = require('./config/init')(),
    config = require('./config/config'),
    mongoose = require('mongoose'),
    cluster = require('cluster');

var processes = 4;    //Number of processes to run at the same time.

if(cluster.isMaster) {
    for(var i = 0; i < processes; i++) {
        cluster.fork();
    }

    cluster.on('disconnect', function(worker) {
        console.error("Disconnect!");
        cluster.fork();
    });
} else {
    /**
     * Main application entry file.
     * Please note that the order of loading is important.
     */

    // Bootstrap db connection
    var db = mongoose.connect(config.db, function(err) {
        if (err) {
            console.error('\x1b[31m', 'Could not connect to MongoDB!');
            console.log(err);
        }
    });

    // Init the express application
    var expressConfig = require('./config/express');
    var app = expressConfig.initialize(db);

    app.use(function(err, req, res, next) {
      console.error(err);
      res.send(401).json({your_message_buddy: "Nice try, idiot."});
    });


    // Bootstrap passport config
    require('./config/passport')();

    // Start the app by listening on <port>
    expressConfig.setServer(app.listen(config.port));

    // Expose app
    exports = module.exports = app;

    // Logging initialization
    console.log('MEAN.JS application started on port ' + config.port);
}

The necessary changes for config/express.js:

var domain = require('domain'),
    cluster = require('cluster');

var appServer = null;

module.exports = {};

/**
* Since we begin listening for requests in server.js, we need a way to
* access the server returned from app.listen() if we want to close the
* server after an error.  To accomplish this, I added this function to
* pass the server object after we begin listening.
*/
module.exports.setServer = function(server) {
    appServer = server;
};

module.exports.initialize = function(db) {
    //Initialize express app
    var app = express();

    //Globbing model files
    config.getGlobbedFiles('./app/models/**/*.js').forEach(function(modelPath) {
        require(path.resolve(modelPath));
    });

    //Set up domain for request BEFORE setting up any other middleware.
    app.use(function(req, res, next) {
        //Create domain for this request
        var reqdomain = domain.create();
        reqdomain.on('error', function(err) {
            console.error('Error: ', err.stack);

            try {
                //Shut down the process within 30 seconds to avoid errors.
                var killtimer = setTimeout(function() {
                    console.error("Failsafe shutdown.");
                    process.exit(1);
                }, 30000);

                //No need to let the process live just for the timer.
                killtimer.unref();

                //No more requests should be allowed for this process.
                appServer.close();

                //Tell master we have died so he can get another worker started.
                if(cluster.worker) {
                    cluster.worker.disconnect();
                }

                //Send an error to the request that caused this failure.
                res.statusCode = 500;
                res.setHeader('Content-Type', 'text/plain');
                res.end('Oops, there was a problem.  How embarrassing.');
            } catch(err2) {
                //Well, something is pretty screwed up.  Not much we can do now.
                console.error('Error sending 500!\nError2: ', err2.stack);
            }
        });

        //Add request and response objects to domain.
        reqdomain.add(req);
        reqdomain.add(res);

        //Execute the rest of the request chain in the domain.
        reqdomain.run(next);
    });

    //The rest of this function, which used to be module.exports, is the same.
};
c1moore
  • 1,827
  • 17
  • 27