Skip to content

Commit f9a47de

Browse files
AndreasMadsenry
authored andcommitted
Add cluster.setupMaster
Fixes #2470
1 parent 6b58537 commit f9a47de

5 files changed

Lines changed: 294 additions & 114 deletions

File tree

doc/api/cluster.markdown

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,40 @@ This can be used to restart the worker by calling `fork()` again.
101101
cluster.fork();
102102
});
103103

104+
### Event 'setup'
105+
106+
When the `.setupMaster()` function has been executed this event emits. If `.setupMaster()`
107+
was not executed before `fork()` or `.autoFork()`, they will execute the function with no
108+
arguments.
109+
110+
### cluster.setupMaster([options])
111+
112+
The `setupMaster` is used to change the default 'fork' behavior. It takes one option
113+
object argument.
114+
115+
Example:
116+
117+
var cluster = require("cluster");
118+
cluster.setupMaster({
119+
exec : "worker.js",
120+
args : ["--use", "https"],
121+
silent : true
122+
});
123+
cluster.autoFork();
124+
125+
The options argument can contain 3 different properties.
126+
127+
- `exec` are the file path to the worker file, by default this is the same file as the master.
128+
- `args` are a array of arguments send along with the worker, by default this is `process.argv.slice(2)`.
129+
- `silent`, if this option is true the output of a worker won't propagate to the master, by default this is false.
130+
131+
### cluster.settings
132+
133+
All settings set by the `.setupMaster` is stored in this settings object.
134+
This object is not supposed to be change or set manually, by you.
135+
136+
All propertys are `undefined` if they are not yet set.
137+
104138
### cluster.fork([env])
105139

106140
Spawn a new worker process. This can only be called from the master process.

lib/cluster.js

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,6 @@ var cluster = module.exports = new cluster();
6161
var masterStarted = false;
6262
var ids = 0;
6363
var serverHandlers = {};
64-
var workerFilename;
65-
var workerArgs;
6664

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

79+
// Settings object
80+
var settings = cluster.settings = {};
81+
8182
// Simple function there call a function on each worker
8283
function eachWorker(cb) {
8384
// Go througe all workers
@@ -88,36 +89,44 @@ function eachWorker(cb) {
8889
}
8990
}
9091

91-
// Call this from the master process. It will start child workers.
92-
//
93-
// options.workerFilename
94-
// Specifies the script to execute for the child processes. Default is
95-
// process.argv[1]
96-
//
97-
// options.args
98-
// Specifies program arguments for the workers. The Default is
99-
// process.argv.slice(2)
100-
//
101-
// options.workers
102-
// The number of workers to start. Defaults to os.cpus().length.
103-
function startMaster() {
104-
// This can only be called from the master.
105-
assert(cluster.isMaster);
92+
cluster.setupMaster = function(options) {
93+
// This can only be called from the master.
94+
assert(cluster.isMaster);
10695

107-
if (masterStarted) return;
108-
masterStarted = true;
96+
// Don't allow this function to run more that once
97+
if (masterStarted) return;
98+
masterStarted = true;
10999

110-
workerFilename = process.argv[1];
111-
workerArgs = process.argv.slice(2);
100+
// Get filename and arguments
101+
options = options || {};
102+
103+
// Set settings object
104+
settings = cluster.settings = {
105+
exec: options.exec || process.argv[1],
106+
args: options.args || process.argv.slice(2),
107+
silent: options.silent || false
108+
};
112109

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

115+
// Output the error stack, and create on if non exist
116+
if (!(err instanceof Error)) {
117+
err = new Error(err);
118+
}
119+
console.error(err.stack);
120+
121+
// quick destroy cluster
117122
quickDestroyCluster();
123+
// when done exit process with error code: 1
118124
process.exit(1);
119-
});
120-
}
125+
});
126+
127+
// emit setup event
128+
cluster.emit('setup');
129+
};
121130

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

277286
// fork worker
278-
this.process = fork(workerFilename, workerArgs, {
279-
'env': envCopy
287+
this.process = fork(settings.exec, settings.args, {
288+
'env': envCopy,
289+
'silent': settings.silent
280290
});
281-
282291
} else {
283292
this.process = process;
284293
}
@@ -426,7 +435,7 @@ cluster.fork = function(env) {
426435
assert(cluster.isMaster);
427436

428437
// Make sure that the master has been initalized
429-
startMaster();
438+
cluster.setupMaster();
430439

431440
return (new cluster.Worker(env));
432441
};

test/simple/test-cluster-kill-workers.js

Lines changed: 0 additions & 84 deletions
This file was deleted.
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// Copyright Joyent, Inc. and other Node contributors.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a
4+
// copy of this software and associated documentation files (the
5+
// "Software"), to deal in the Software without restriction, including
6+
// without limitation the rights to use, copy, modify, merge, publish,
7+
// distribute, sublicense, and/or sell copies of the Software, and to permit
8+
// persons to whom the Software is furnished to do so, subject to the
9+
// following conditions:
10+
//
11+
// The above copyright notice and this permission notice shall be included
12+
// in all copies or substantial portions of the Software.
13+
//
14+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15+
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17+
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18+
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20+
// USE OR OTHER DEALINGS IN THE SOFTWARE.
21+
22+
23+
var common = require('../common');
24+
var assert = require('assert');
25+
var cluster = require('cluster');
26+
27+
// Cluster setup
28+
if (cluster.isWorker) {
29+
var http = require('http');
30+
http.Server(function() {
31+
32+
}).listen(common.PORT, '127.0.0.1');
33+
34+
} else if (process.argv[2] === 'cluster') {
35+
36+
var totalWorkers = 2;
37+
38+
// Send PID to testcase process
39+
var forkNum = 0;
40+
cluster.on('fork', function forkEvent(worker) {
41+
42+
// Send PID
43+
process.send({
44+
cmd: 'worker',
45+
workerPID: worker.process.pid
46+
});
47+
48+
// Stop listening when done
49+
if (++forkNum === totalWorkers) {
50+
cluster.removeListener('fork', forkEvent);
51+
}
52+
});
53+
54+
// Throw accidently error when all workers are listening
55+
var listeningNum = 0;
56+
cluster.on('listening', function listeningEvent() {
57+
58+
// When all workers are listening
59+
if (++listeningNum === totalWorkers) {
60+
// Stop listening
61+
cluster.removeListener('listening', listeningEvent);
62+
63+
// throw accidently error
64+
process.nextTick(function() {
65+
throw 'accidently error';
66+
});
67+
}
68+
69+
});
70+
71+
// Startup a basic cluster
72+
cluster.fork();
73+
cluster.fork();
74+
75+
} else {
76+
// This is the testcase
77+
78+
var fork = require('child_process').fork;
79+
80+
var isAlive = function(pid) {
81+
try {
82+
//this will throw an error if the process is dead
83+
process.kill(pid, 0);
84+
85+
return true;
86+
} catch (e) {
87+
return false;
88+
}
89+
};
90+
91+
var existMaster = false;
92+
var existWorker = false;
93+
94+
// List all workers
95+
var workers = [];
96+
97+
// Spawn a cluster process
98+
var master = fork(process.argv[1], ['cluster'], {silent: true});
99+
100+
// Handle messages from the cluster
101+
master.on('message', function(data) {
102+
103+
// Add worker pid to list and progress tracker
104+
if (data.cmd === 'worker') {
105+
workers.push(data.workerPID);
106+
}
107+
});
108+
109+
// When cluster is dead
110+
master.on('exit', function(code) {
111+
112+
// Check that the cluster died accidently
113+
existMaster = (code === 1);
114+
115+
// When master is dead all workers should be dead to
116+
var alive = false;
117+
workers.forEach(function(pid) {
118+
if (isAlive(pid)) {
119+
alive = true;
120+
}
121+
});
122+
123+
// If a worker was alive this did not act as expected
124+
existWorker = !alive;
125+
});
126+
127+
process.once('exit', function() {
128+
assert.ok(existMaster, 'The master did not die after an error was throwed');
129+
assert.ok(existWorker, 'The workers did not die after an error in the master');
130+
});
131+
132+
}

0 commit comments

Comments
 (0)