Shutting down a NodeJS Cluster

1,834 views
Skip to first unread message

phidelta

unread,
Dec 8, 2011, 5:09:29 PM12/8/11
to nodejs
Hi all,

anyone know how to shut down a node cluster?


When I run this:

var http = require('http');
var server = http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n');
});
server.listen(1337, "127.0.0.1");

process.once('SIGQUIT', function() {
console.log("Closing "+process.pid);
server.close();
});

console.log("I am "+process.pid);

Everything works as expected. I send a SIGQUIT to the process, it
closes the server, and because there are no more possible events, node
quits (I love this feature).

A sample output is:
#> node test.js
I am 930
Closing 930
#>

However when I add the following code to the end of the above code:


var cluster = require('cluster');
if (cluster.isMaster) {
var worker = cluster.fork();
process.once('SIGQUIT',function() {
console.log("Passing SIGQUIT to "+worker.pid);
worker.kill('SIGQUIT');
});
}

The output is the following:
#> node test.js
I am 941
Worker 942 online
I am 942
Closing 941
Passing SIGQUIT to 942
Closing 942

After which node is still around, basically forever (for a value of
forever >=3 minutes, which is the limit of my patience).

When looking at the processes, via
#> lsof -p 941,942

I noticed, that the IPC pipe between the processes still seems to be
open. So my question is, how do I tell the master/workers that they
are done with their works and should shut down as soon as their main
work is complete.

What I want is that the http-servers in the workers complete request
that may be in process, and only when they are done, exit. Just like
the single process model does it.

Is this possible, should happen automatically anyway? Anyone have any
insight here?

Regards, Phil

phidelta

unread,
Dec 8, 2011, 5:38:18 PM12/8/11
to nodejs
One more observation that goes against the hypothesis I wrote above.

When using the Signal-Handler to fs.close(0) in the worker and
worker._channel.close() in the master the process still remains alive.
So that makes me think it may not be the pipe, or at least not just
the pipe.

Hmmmm

phidelta

unread,
Dec 8, 2011, 6:22:35 PM12/8/11
to nodejs
Ok,

I figured out that this works:

var cluster = require('cluster');
if (cluster.isMaster) {
var worker = cluster.fork();
process.once('SIGQUIT',function() {
console.log("Passing SIGQUIT to "+worker.pid);
worker.kill('SIGQUIT');

console.log("Closing Worker-Channel in Master");
worker._channel.close();
worker._channel.unref();
});
} else {
process.once('SIGQUIT',function() {
console.log("Closing Master-Channel in Child");
process._channel.close();
process._channel.unref();
require("fs").close(0);
});
}

However it uses the "private" _channel of the worker (in the master)
and of process (in the worker), which is subject to change.
Is this just a lacking API?

should we have something like:
// cluster.js # from line 70
var shutdown = false;
function startMaster() {
// This can only be called from the master.
assert(cluster.isMaster);

if (masterStarted) return;
masterStarted = true;

workerFilename = process.argv[1];
workerArgs = process.argv.slice(2);

process.on('uncaughtException', function(e) {
// Quickly try to kill all the workers.
// TODO: be session leader - will cause auto SIGHUP to the
children.
eachWorker(function(worker) {
debug("kill worker " + worker.pid);
worker.kill();
})

console.error("Exception in cluster master process: " +
e.message + '\n' + e.stack);
process.exit(1);
});
cluster.shutdown = function() {
if (shutdown) return;
shutdown = true;
eachWorker(function() {
worker._channel.close();
worker._channel.unref();
});
}
}

// cluster.js # from line 177
var fdClose=require('fs').close;
cluster._startWorker = function() {
assert(cluster.isWorker);
workerId = parseInt(process.env.NODE_WORKER_ID);

queryMaster({ cmd: 'online' });

// Make callbacks from queryMaster()
process.on('message', function(msg, handle) {
debug("recv " + JSON.stringify(msg));
if (shutdown) return;
if (msg.cmd == 'shutdown') {
shutdown = true;
process._channel.close();
process._channel.unref();
fdClose(0);
}
if (msg._queryId && msg._queryId in queryCallbacks) {
var cb = queryCallbacks[msg._queryId];
if (typeof cb == 'function') {
cb(msg, handle);
}
delete queryCallbacks[msg._queryId]
}
});
};


function queryMaster(msg, cb) {
assert(cluster.isWorker);

debug('send ' + JSON.stringify(msg));

if (shutdown) return;
// Grab some random queryId
msg._queryId = (++queryIds);
msg._workerId = workerId;

// Store callback for later. Callback called in _startWorker.
if (cb) {
queryCallbacks[msg._queryId] = cb;
}

// Send message to master.
process.send(msg);
}

On second thought I think I'll move this to nodes-dev.

Regards, Phil

Colder Xihk

unread,
Dec 14, 2011, 1:13:52 AM12/14/11
to nod...@googlegroups.com
I wrote a cake task for development express easy. I watch models/routes and so on. Then restart cluster when files changed.

Your code works for me. Thank you.
Reply all
Reply to author
Forward
0 new messages