Browse Source

cluster: worker exit event to match child_process

test: fixes due to new cluster api.

- changed worker `death` to `exit`.
- corrected argument type expected by worker `exit` handler.

test: more tests of cluster.worker death

cluster: fixed arguments on worker 'exit' event

worker 'exit' event now emits arguments consistent with the
corresponding event in child_process module.
v0.9.1-release
J. Lee Coltrane 13 years ago
committed by isaacs
parent
commit
a62dd44b20
  1. 26
      doc/api/cluster.markdown
  2. 19
      lib/cluster.js
  3. 10
      test/simple/test-cluster-basic.js
  4. 7
      test/simple/test-cluster-worker-death.js
  5. 152
      test/simple/test-cluster-worker-exit.js
  6. 149
      test/simple/test-cluster-worker-kill.js

26
doc/api/cluster.markdown

@ -148,7 +148,8 @@ When any of the workers die the cluster module will emit the 'exit' event.
This can be used to restart the worker by calling `fork()` again.
cluster.on('exit', function(worker) {
console.log('worker ' + worker.pid + ' died. restart...');
var exitCode = worker.process.exitCode;
console.log('worker ' + worker.pid + ' died ('+exitCode+'). restarting...');
cluster.fork();
});
@ -436,11 +437,20 @@ on the specified worker.
### Event: 'exit'
* `worker` {Worker object}
Same as the `cluster.on('exit')` event, but emits only when the state change
on the specified worker.
cluster.fork().on('exit', function (worker) {
// Worker has died
* `code` {Number} the exit code, if it exited normally.
* `signal` {String} the name of the signal (eg. `'SIGHUP'`) that caused
the process to be killed.
Emitted by the individual worker instance, when the underlying child process
is terminated. See [child_process event: 'exit'](child_process.html#child_process_event_exit).
var worker = cluster.fork();
worker.on('exit', function (code, signal) {
if( signal ) {
console.log("worker was killed by signal: "+signal);
} else if( code !== 0 ) {
console.log("worker exited with error code: "+code);
} else {
console.log("worker success!");
}
};

19
lib/cluster.js

@ -294,9 +294,16 @@ function Worker(customEnv) {
// handle internalMessage, exit and disconnect event
this.process.on('internalMessage', handleMessage.bind(null, this));
this.process.on('exit', prepareExit.bind(null, this, 'dead', 'exit'));
this.process.on('disconnect',
prepareExit.bind(null, this, 'disconnected', 'disconnect'));
this.process.once('exit', function(exitCode, signalCode) {
prepareExit(self, 'dead');
self.emit('exit', exitCode, signalCode);
cluster.emit('exit', self);
});
this.process.once('disconnect', function() {
prepareExit(self, 'disconnected');
self.emit('disconnect');
cluster.emit('disconnect', self);
});
// relay message and error
this.process.on('message', this.emit.bind(this, 'message'));
@ -306,7 +313,7 @@ function Worker(customEnv) {
util.inherits(Worker, EventEmitter);
cluster.Worker = Worker;
function prepareExit(worker, state, eventName) {
function prepareExit(worker, state) {
// set state to disconnect
worker.state = state;
@ -318,10 +325,6 @@ function prepareExit(worker, state, eventName) {
if (cluster.isMaster) {
delete cluster.workers[worker.uniqueID];
}
// Emit events
worker.emit(eventName, worker);
cluster.emit(eventName, worker);
}
// Send internal message

10
test/simple/test-cluster-basic.js

@ -122,7 +122,15 @@ else if (cluster.isMaster) {
checks.worker.events[name] = true;
//Check argument
checks.worker.equal[name] = worker === arguments[0];
if (name == 'exit') {
checks.worker.equal[name] = (
worker.process.exitCode === arguments[0] &&
worker.process.signalCode === arguments[1] &&
worker === this
);
} else {
checks.worker.equal[name] = worker === arguments[0];
}
});
});

7
test/simple/test-cluster-worker-death.js

@ -30,11 +30,12 @@ else {
var seenExit = 0;
var seenDeath = 0;
var worker = cluster.fork();
worker.on('exit', function(statusCode) {
assert.equal(statusCode, 42);
worker.on('exit', function(exitCode, signalCode) {
assert.equal(exitCode, 42);
assert.equal(signalCode, null);
seenExit++;
});
cluster.on('death', function(worker_) {
cluster.on('exit', function(worker_) {
assert.equal(worker_, worker);
seenDeath++;
});

152
test/simple/test-cluster-worker-exit.js

@ -0,0 +1,152 @@
// 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.
// test-cluster-worker-exit.js
// verifies that, when a child process exits (by calling `process.exit(code)`)
// - the parent receives the proper events in the proper order, no duplicates
// - the exitCode and signalCode are correct in the 'exit' event
// - the worker.suicide flag, and worker.state are correct
// - the worker process actually goes away
var common = require('../common');
var assert = require('assert');
var cluster = require('cluster');
var EXIT_CODE = 42;
if (cluster.isWorker) {
var http = require('http');
var server = http.Server(function() { });
server.once('listening', function() {
process.exit(EXIT_CODE);
});
server.listen(common.PORT, '127.0.0.1');
} else if (cluster.isMaster) {
var expected_results = {
cluster_emitDisconnect: [1, "the cluster did not emit 'disconnect'"],
cluster_emitExit: [1, "the cluster did not emit 'exit'"],
cluster_exitCode: [EXIT_CODE, 'the cluster exited w/ incorrect exitCode'],
cluster_signalCode: [null, 'the cluster exited w/ incorrect signalCode'],
worker_emitDisconnect: [1, "the worker did not emit 'disconnect'"],
worker_emitExit: [1, "the worker did not emit 'exit'"],
worker_state: ['disconnected', 'the worker state is incorrect'],
worker_suicideMode: [false, 'the worker.suicide flag is incorrect'],
worker_died: [true, 'the worker is still running'],
worker_exitCode: [EXIT_CODE, 'the worker exited w/ incorrect exitCode'],
worker_signalCode: [null, 'the worker exited w/ incorrect signalCode']
};
var results = {
cluster_emitDisconnect: 0,
cluster_emitExit: 0,
worker_emitDisconnect: 0,
worker_emitExit: 0
};
// start worker
var worker = cluster.fork();
worker.once('listening', function() {
// the worker is up and running...
});
// Check cluster events
cluster.on('disconnect', function() {
results.cluster_emitDisconnect += 1;
});
cluster.on('exit', function(worker) {
results.cluster_exitCode = worker.process.exitCode;
results.cluster_signalCode = worker.process.signalCode;
results.cluster_emitExit += 1;
assert.ok(results.cluster_emitDisconnect,
"cluster: 'exit' event before 'disconnect' event");
});
// Check worker eventes and properties
worker.on('disconnect', function() {
results.worker_emitDisconnect += 1;
results.worker_suicideMode = worker.suicide;
results.worker_state = worker.state;
});
// Check that the worker died
worker.once('exit', function(exitCode, signalCode) {
results.worker_exitCode = exitCode;
results.worker_signalCode = signalCode;
results.worker_emitExit += 1;
results.worker_died = !alive(worker.process.pid);
assert.ok(results.worker_emitDisconnect,
"worker: 'exit' event before 'disconnect' event");
process.nextTick(function() { finish_test(); });
});
var finish_test = function() {
try {
checkResults(expected_results, results);
} catch (exc) {
console.error('FAIL: ' + exc.message);
if (exc.name != 'AssertionError') {
console.trace(exc);
}
process.exit(1);
return;
}
process.exit(0);
};
}
// some helper functions ...
function checkResults(expected_results, results) {
for (var k in expected_results) {
var actual = results[k],
expected = expected_results[k];
if (typeof expected === 'function') {
expected(r[k]);
} else {
var msg = (expected[1] || '') +
(' [expected: ' + expected[0] + ' / actual: ' + actual + ']');
if (expected && expected.length) {
assert.equal(actual, expected[0], msg);
} else {
assert.equal(actual, expected, msg);
}
}
}
}
function alive(pid) {
try {
process.kill(pid, 'SIGCONT');
return true;
} catch (e) {
return false;
}
}

149
test/simple/test-cluster-worker-kill.js

@ -0,0 +1,149 @@
// 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.
// test-cluster-worker-kill.js
// verifies that, when a child process is killed (we use SIGHUP)
// - the parent receives the proper events in the proper order, no duplicates
// - the exitCode and signalCode are correct in the 'exit' event
// - the worker.suicide flag, and worker.state are correct
// - the worker process actually goes away
var common = require('../common');
var assert = require('assert');
var cluster = require('cluster');
if (cluster.isWorker) {
var http = require('http');
var server = http.Server(function() { });
server.once('listening', function() { });
server.listen(common.PORT, '127.0.0.1');
} else if (cluster.isMaster) {
var KILL_SIGNAL = 'SIGHUP',
expected_results = {
cluster_emitDisconnect: [1, "the cluster did not emit 'disconnect'"],
cluster_emitExit: [1, "the cluster did not emit 'exit'"],
cluster_exitCode: [null, 'the cluster exited w/ incorrect exitCode'],
cluster_signalCode: [KILL_SIGNAL, 'the cluster exited w/ incorrect signalCode'],
worker_emitDisconnect: [1, "the worker did not emit 'disconnect'"],
worker_emitExit: [1, "the worker did not emit 'exit'"],
worker_state: ['disconnected', 'the worker state is incorrect'],
worker_suicideMode: [false, 'the worker.suicide flag is incorrect'],
worker_died: [true, 'the worker is still running'],
worker_exitCode: [null, 'the worker exited w/ incorrect exitCode'],
worker_signalCode: [KILL_SIGNAL, 'the worker exited w/ incorrect signalCode']
},
results = {
cluster_emitDisconnect: 0,
cluster_emitExit: 0,
worker_emitDisconnect: 0,
worker_emitExit: 0
};
// start worker
var worker = cluster.fork();
// when the worker is up and running, kill it
worker.once('listening', function() {
worker.process.kill(KILL_SIGNAL);
});
// Check cluster events
cluster.on('disconnect', function() {
results.cluster_emitDisconnect += 1;
});
cluster.on('exit', function(worker) {
results.cluster_exitCode = worker.process.exitCode;
results.cluster_signalCode = worker.process.signalCode;
results.cluster_emitExit += 1;
assert.ok(results.cluster_emitDisconnect,
"cluster: 'exit' event before 'disconnect' event");
});
// Check worker eventes and properties
worker.on('disconnect', function() {
results.worker_emitDisconnect += 1;
results.worker_suicideMode = worker.suicide;
results.worker_state = worker.state;
});
// Check that the worker died
worker.once('exit', function(exitCode, signalCode) {
results.worker_exitCode = exitCode;
results.worker_signalCode = signalCode;
results.worker_emitExit += 1;
results.worker_died = !alive(worker.process.pid);
assert.ok(results.worker_emitDisconnect,
"worker: 'exit' event before 'disconnect' event");
process.nextTick(function() { finish_test(); });
});
var finish_test = function() {
try {
checkResults(expected_results, results);
} catch (exc) {
console.error('FAIL: ' + exc.message);
if (exc.name != 'AssertionError') {
console.trace(exc);
}
process.exit(1);
return;
}
process.exit(0);
};
}
// some helper functions ...
function checkResults(expected_results, results) {
for (var k in expected_results) {
var actual = results[k],
expected = expected_results[k];
if (typeof expected === 'function') {
expected(r[k]);
} else {
var msg = (expected[1] || '') +
(' [expected: ' + expected[0] + ' / actual: ' + actual + ']');
if (expected && expected.length) {
assert.equal(actual, expected[0], msg);
} else {
assert.equal(actual, expected, msg);
}
}
}
}
function alive(pid) {
try {
process.kill(pid, 'SIGCONT');
return true;
} catch (e) {
return false;
}
}
Loading…
Cancel
Save