Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Upgrade Node.JS web app without restarting

Is there a way to make changes to the code of a Node.JS application without restarting it.

For an example, if a file is being downloaded from the server at that time, is there a way to upgrade the server without the file transfer being disrupted?

like image 344
Sanka Darshana Avatar asked Feb 14 '23 00:02

Sanka Darshana


1 Answers

Yes, it's possible with the cluster module.

Basically, you will start several slave instances of your app sharing the same port through a master process. The master will distribute incoming connections to the slaves.

When you want to restart, the master will, in sequence:

  • stop passing connections to a slave
  • tell it gently to restart
  • wait for it to respawn

Here's some code to get you started (you'll need to npm install async underscore).

Note: This is just to get you started. You should be prepared to handle unexpected slave failures and timeouts in a real setup.

var cluster = require('cluster');
var http = require('http');
var _ = require('underscore');
var async = require('async');
var numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
  console.log('starting master with PID', process.pid);
  // Fork workers.
  var slaves = {};

  for (var i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on('fork', function (worker) {
    slaves[worker.id] = worker;
  });

  cluster.on('exit', function (worker, code, signal) {
    console.log('worker ' + worker.process.pid + ' died');
    delete slaves[worker.id];

    // restart the worker
    cluster.fork();
  });

  process.on('SIGHUP', function restartApp() {
    console.log('restarting all slaves');
    // important: force node.js to reload all js sources
    delete require.cache;

    var toRestart = _(slaves).values();

    async.eachSeries(toRestart, function (slave, done) {
      slave.kill('SIGTERM');

      // when the new worker starts, proceed to the next instance
      cluster.once('listening', function () {
        done();
      });
    });
  });
} else {
  console.log('- starting slave with PID', process.pid);
  // Workers can share any TCP connection
  // In this case its a HTTP server
  var server = http.createServer(function(req, res) {
    res.writeHead(200);
    res.end("hello world\n");
  });

  process.on('SIGTERM', function () {
    // finish all current connections, then stop
    server.close(function () {
      process.exit(0);
    });
  });

  server.listen(8000);
}

When you call `kill -HUP , you'll see that it restart all slaves in sequence:

worker 5156 died
- starting slave with PID 5164
worker 5157 died
- starting slave with PID 5165
worker 5158 died
- starting slave with PID 5166
worker 5159 died
- starting slave with PID 5167

While restarting, there are always 3 running slaves to handle incoming connections.

One last thing: in case you try, console.log doesn't do anything in the slave SIGTERM handler because it has already been disconnected from the master.

like image 83
Laurent Perrin Avatar answered Feb 16 '23 11:02

Laurent Perrin