Skip to content

Commit

Permalink
Add cluster.setupMaster
Browse files Browse the repository at this point in the history
Fixes #2470
  • Loading branch information
AndreasMadsen authored and ry committed Jan 20, 2012
1 parent 6b58537 commit f9a47de
Show file tree
Hide file tree
Showing 5 changed files with 294 additions and 114 deletions.
34 changes: 34 additions & 0 deletions doc/api/cluster.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,40 @@ This can be used to restart the worker by calling `fork()` again.
cluster.fork();
});

### Event 'setup'

When the `.setupMaster()` function has been executed this event emits. If `.setupMaster()`
was not executed before `fork()` or `.autoFork()`, they will execute the function with no
arguments.

### cluster.setupMaster([options])

The `setupMaster` is used to change the default 'fork' behavior. It takes one option
object argument.

Example:

var cluster = require("cluster");
cluster.setupMaster({
exec : "worker.js",
args : ["--use", "https"],
silent : true
});
cluster.autoFork();

The options argument can contain 3 different properties.

- `exec` are the file path to the worker file, by default this is the same file as the master.
- `args` are a array of arguments send along with the worker, by default this is `process.argv.slice(2)`.
- `silent`, if this option is true the output of a worker won't propagate to the master, by default this is false.

### cluster.settings

All settings set by the `.setupMaster` is stored in this settings object.
This object is not supposed to be change or set manually, by you.

All propertys are `undefined` if they are not yet set.

### cluster.fork([env])

Spawn a new worker process. This can only be called from the master process.
Expand Down
69 changes: 39 additions & 30 deletions lib/cluster.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,6 @@ var cluster = module.exports = new cluster();
var masterStarted = false;
var ids = 0;
var serverHandlers = {};
var workerFilename;
var workerArgs;

// Used in the worker:
var serverLisenters = {};
Expand All @@ -78,6 +76,9 @@ cluster.worker = cluster.isWorker ? {} : null;
// The workers array is oly used in the naster
cluster.workers = cluster.isMaster ? {} : null;

// Settings object
var settings = cluster.settings = {};

// Simple function there call a function on each worker
function eachWorker(cb) {
// Go througe all workers
Expand All @@ -88,36 +89,44 @@ function eachWorker(cb) {
}
}

// Call this from the master process. It will start child workers.
//
// options.workerFilename
// Specifies the script to execute for the child processes. Default is
// process.argv[1]
//
// options.args
// Specifies program arguments for the workers. The Default is
// process.argv.slice(2)
//
// options.workers
// The number of workers to start. Defaults to os.cpus().length.
function startMaster() {
// This can only be called from the master.
assert(cluster.isMaster);
cluster.setupMaster = function(options) {
// This can only be called from the master.
assert(cluster.isMaster);

if (masterStarted) return;
masterStarted = true;
// Don't allow this function to run more that once
if (masterStarted) return;
masterStarted = true;

workerFilename = process.argv[1];
workerArgs = process.argv.slice(2);
// Get filename and arguments
options = options || {};

// Set settings object
settings = cluster.settings = {
exec: options.exec || process.argv[1],
args: options.args || process.argv.slice(2),
silent: options.silent || false
};

process.on('uncaughtException', function(e) {
console.error('Exception in cluster master process: ' +
e.message + '\n' + e.stack);
// Kill workers when a uncaught exception is received
process.on('uncaughtException', function(err) {
// Did the user install a listener? If so, it overrides this one.
if (process.listeners('uncaughtException').length > 1) return;

// Output the error stack, and create on if non exist
if (!(err instanceof Error)) {
err = new Error(err);
}
console.error(err.stack);

// quick destroy cluster
quickDestroyCluster();
// when done exit process with error code: 1
process.exit(1);
});
}
});

// emit setup event
cluster.emit('setup');
};

// Check if a message is internal only
var INTERNAL_PREFIX = 'NODE_CLUSTER_';
Expand Down Expand Up @@ -275,10 +284,10 @@ function Worker(customEnv) {
}

// fork worker
this.process = fork(workerFilename, workerArgs, {
'env': envCopy
this.process = fork(settings.exec, settings.args, {
'env': envCopy,
'silent': settings.silent
});

} else {
this.process = process;
}
Expand Down Expand Up @@ -426,7 +435,7 @@ cluster.fork = function(env) {
assert(cluster.isMaster);

// Make sure that the master has been initalized
startMaster();
cluster.setupMaster();

return (new cluster.Worker(env));
};
Expand Down
84 changes: 0 additions & 84 deletions test/simple/test-cluster-kill-workers.js

This file was deleted.

132 changes: 132 additions & 0 deletions test/simple/test-cluster-master-error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.


var common = require('../common');
var assert = require('assert');
var cluster = require('cluster');

// Cluster setup
if (cluster.isWorker) {
var http = require('http');
http.Server(function() {

}).listen(common.PORT, '127.0.0.1');

} else if (process.argv[2] === 'cluster') {

var totalWorkers = 2;

// Send PID to testcase process
var forkNum = 0;
cluster.on('fork', function forkEvent(worker) {

// Send PID
process.send({
cmd: 'worker',
workerPID: worker.process.pid
});

// Stop listening when done
if (++forkNum === totalWorkers) {
cluster.removeListener('fork', forkEvent);
}
});

// Throw accidently error when all workers are listening
var listeningNum = 0;
cluster.on('listening', function listeningEvent() {

// When all workers are listening
if (++listeningNum === totalWorkers) {
// Stop listening
cluster.removeListener('listening', listeningEvent);

// throw accidently error
process.nextTick(function() {
throw 'accidently error';
});
}

});

// Startup a basic cluster
cluster.fork();
cluster.fork();

} else {
// This is the testcase

var fork = require('child_process').fork;

var isAlive = function(pid) {
try {
//this will throw an error if the process is dead
process.kill(pid, 0);

return true;
} catch (e) {
return false;
}
};

var existMaster = false;
var existWorker = false;

// List all workers
var workers = [];

// Spawn a cluster process
var master = fork(process.argv[1], ['cluster'], {silent: true});

// Handle messages from the cluster
master.on('message', function(data) {

// Add worker pid to list and progress tracker
if (data.cmd === 'worker') {
workers.push(data.workerPID);
}
});

// When cluster is dead
master.on('exit', function(code) {

// Check that the cluster died accidently
existMaster = (code === 1);

// When master is dead all workers should be dead to
var alive = false;
workers.forEach(function(pid) {
if (isAlive(pid)) {
alive = true;
}
});

// If a worker was alive this did not act as expected
existWorker = !alive;
});

process.once('exit', function() {
assert.ok(existMaster, 'The master did not die after an error was throwed');
assert.ok(existWorker, 'The workers did not die after an error in the master');
});

}
Loading

0 comments on commit f9a47de

Please sign in to comment.