parent
6b5853794d
commit
f9a47debfc
@ -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.
|
||||
|
@ -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 = {};
|
||||
@ -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
|
||||
@ -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 || {};
|
||||
|
||||
process.on('uncaughtException', function(e) {
|
||||
console.error('Exception in cluster master process: ' +
|
||||
e.message + '\n' + e.stack);
|
||||
// Set settings object
|
||||
settings = cluster.settings = {
|
||||
exec: options.exec || process.argv[1],
|
||||
args: options.args || process.argv.slice(2),
|
||||
silent: options.silent || false
|
||||
};
|
||||
|
||||
// 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_';
|
||||
@ -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;
|
||||
}
|
||||
@ -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));
|
||||
};
|
||||
|
@ -1,84 +0,0 @@
|
||||
// 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.
|
||||
|
||||
// This test checks that if we kill a cluster master immediately after fork,
|
||||
// before the worker has time to register itself, that the master will still
|
||||
// clean up the worker.
|
||||
// https://github.com/joyent/node/issues/2047
|
||||
|
||||
var common = require('../common');
|
||||
var assert = require('assert');
|
||||
var cluster = require('cluster');
|
||||
var fork = require('child_process').fork;
|
||||
|
||||
var isTestRunner = process.argv[2] != 'child';
|
||||
|
||||
if (isTestRunner) {
|
||||
console.log("starting master...");
|
||||
var master = fork(__filename, [ 'child' ]);
|
||||
|
||||
console.log("master pid =", master.pid);
|
||||
|
||||
var workerPID;
|
||||
|
||||
master.on("message", function(m) {
|
||||
console.log("got message from master:", m);
|
||||
if (m.workerPID) {
|
||||
console.log("worker pid =", m.workerPID);
|
||||
workerPID = m.workerPID;
|
||||
}
|
||||
});
|
||||
|
||||
var gotExit = false;
|
||||
var gotKillException = false;
|
||||
|
||||
master.on('exit', function(code) {
|
||||
gotExit = true;
|
||||
assert(code != 0);
|
||||
assert(workerPID > 0);
|
||||
try {
|
||||
process.kill(workerPID, 0);
|
||||
} catch(e) {
|
||||
// workerPID is no longer running
|
||||
console.log(e)
|
||||
assert(e.code == 'ESRCH');
|
||||
gotKillException = true;
|
||||
}
|
||||
})
|
||||
|
||||
process.on('exit', function() {
|
||||
assert(gotExit);
|
||||
assert(gotKillException);
|
||||
});
|
||||
} else {
|
||||
// Cluster stuff.
|
||||
if (cluster.isMaster) {
|
||||
var worker = cluster.fork();
|
||||
process.send({ workerPID: worker.process.pid });
|
||||
// should kill the worker too
|
||||
throw new Error('kill master');
|
||||
} else {
|
||||
setTimeout(function() {
|
||||
assert(false, 'worker should have been killed');
|
||||
}, 2500);
|
||||
}
|
||||
}
|
||||
|
132
test/simple/test-cluster-master-error.js
Normal file
132
test/simple/test-cluster-master-error.js
Normal file
@ -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');
|
||||
});
|
||||
|
||||
}
|
89
test/simple/test-cluster-setup-master.js
Normal file
89
test/simple/test-cluster-setup-master.js
Normal file
@ -0,0 +1,89 @@
|
||||
// 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');
|
||||
|
||||
if (cluster.isWorker) {
|
||||
|
||||
// Just keep the worker alive
|
||||
process.send(process.argv[2]);
|
||||
|
||||
} else if (cluster.isMaster) {
|
||||
|
||||
var checks = {
|
||||
args: false,
|
||||
setupEvent: false,
|
||||
settingsObject: false
|
||||
};
|
||||
|
||||
var totalWorkers = 2;
|
||||
|
||||
cluster.once('setup', function() {
|
||||
checks.setupEvent = true;
|
||||
|
||||
var settings = cluster.settings;
|
||||
if (settings &&
|
||||
settings.args && settings.args[0] === 'custom argument' &&
|
||||
settings.silent === true &&
|
||||
settings.exec === process.argv[1]) {
|
||||
checks.settingsObject = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Setup master
|
||||
cluster.setupMaster({
|
||||
args: ['custom argument'],
|
||||
silent: true
|
||||
});
|
||||
|
||||
var correctIn = 0;
|
||||
|
||||
cluster.on('online', function lisenter(worker) {
|
||||
|
||||
worker.once('message', function(data) {
|
||||
correctIn += (data === 'custom argument' ? 1 : 0);
|
||||
if (correctIn === totalWorkers) {
|
||||
checks.args = true;
|
||||
}
|
||||
worker.destroy();
|
||||
});
|
||||
|
||||
// All workers are online
|
||||
if (cluster.onlineWorkers === totalWorkers) {
|
||||
checks.workers = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Start all workers
|
||||
cluster.fork();
|
||||
cluster.fork();
|
||||
|
||||
// Check all values
|
||||
process.once('exit', function() {
|
||||
assert.ok(checks.args, 'The arguments was noy send to the worker');
|
||||
assert.ok(checks.setupEvent, 'The setup event was never emitted');
|
||||
assert.ok(checks.settingsObject, 'The settingsObject do not have correct properties');
|
||||
});
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user