Skip to content
This repository
Browse code

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.
  • Loading branch information...
commit a62dd44b20196d38d5c60840ef823615941a6f13 1 parent c1bf810
Lee Coltrane authored May 02, 2012 isaacs committed May 04, 2012
26  doc/api/cluster.markdown
Source Rendered
@@ -148,7 +148,8 @@ When any of the workers die the cluster module will emit the 'exit' event.
148 148
 This can be used to restart the worker by calling `fork()` again.
149 149
 
150 150
     cluster.on('exit', function(worker) {
151  
-      console.log('worker ' + worker.pid + ' died. restart...');
  151
+      var exitCode = worker.process.exitCode;
  152
+      console.log('worker ' + worker.pid + ' died ('+exitCode+'). restarting...');
152 153
       cluster.fork();
153 154
     });
154 155
 
@@ -436,11 +437,20 @@ on the specified worker.
436 437
 
437 438
 ### Event: 'exit'
438 439
 
439  
-* `worker` {Worker object}
440  
-
441  
-Same as the `cluster.on('exit')` event, but emits only when the state change
442  
-on the specified worker.
443  
-
444  
-    cluster.fork().on('exit', function (worker) {
445  
-      // Worker has died
  440
+* `code` {Number} the exit code, if it exited normally. 
  441
+* `signal` {String} the name of the signal (eg. `'SIGHUP'`) that caused
  442
+  the process to be killed.
  443
+
  444
+Emitted by the individual worker instance, when the underlying child process
  445
+is terminated.  See [child_process event: 'exit'](child_process.html#child_process_event_exit). 
  446
+
  447
+    var worker = cluster.fork();
  448
+    worker.on('exit', function (code, signal) {
  449
+      if( signal ) {
  450
+        console.log("worker was killed by signal: "+signal);
  451
+      } else if( code !== 0 ) {
  452
+        console.log("worker exited with error code: "+code);
  453
+      } else {
  454
+        console.log("worker success!");
  455
+      }
446 456
     };
19  lib/cluster.js
@@ -294,9 +294,16 @@ function Worker(customEnv) {
294 294
 
295 295
   // handle internalMessage, exit and disconnect event
296 296
   this.process.on('internalMessage', handleMessage.bind(null, this));
297  
-  this.process.on('exit', prepareExit.bind(null, this, 'dead', 'exit'));
298  
-  this.process.on('disconnect',
299  
-                  prepareExit.bind(null, this, 'disconnected', 'disconnect'));
  297
+  this.process.once('exit', function(exitCode, signalCode) {
  298
+    prepareExit(self, 'dead');
  299
+    self.emit('exit', exitCode, signalCode);
  300
+    cluster.emit('exit', self);
  301
+  });
  302
+  this.process.once('disconnect', function() {
  303
+    prepareExit(self, 'disconnected');
  304
+    self.emit('disconnect');
  305
+    cluster.emit('disconnect', self);
  306
+  });
300 307
 
301 308
   // relay message and error
302 309
   this.process.on('message', this.emit.bind(this, 'message'));
@@ -306,7 +313,7 @@ function Worker(customEnv) {
306 313
 util.inherits(Worker, EventEmitter);
307 314
 cluster.Worker = Worker;
308 315
 
309  
-function prepareExit(worker, state, eventName) {
  316
+function prepareExit(worker, state) {
310 317
 
311 318
   // set state to disconnect
312 319
   worker.state = state;
@@ -318,10 +325,6 @@ function prepareExit(worker, state, eventName) {
318 325
   if (cluster.isMaster) {
319 326
     delete cluster.workers[worker.uniqueID];
320 327
   }
321  
-
322  
-  // Emit events
323  
-  worker.emit(eventName, worker);
324  
-  cluster.emit(eventName, worker);
325 328
 }
326 329
 
327 330
 // Send internal message
10  test/simple/test-cluster-basic.js
@@ -122,7 +122,15 @@ else if (cluster.isMaster) {
122 122
       checks.worker.events[name] = true;
123 123
 
124 124
       //Check argument
125  
-      checks.worker.equal[name] = worker === arguments[0];
  125
+      if (name == 'exit') {
  126
+        checks.worker.equal[name] = (
  127
+          worker.process.exitCode === arguments[0] &&
  128
+          worker.process.signalCode === arguments[1] &&
  129
+          worker === this
  130
+        );
  131
+      } else {
  132
+        checks.worker.equal[name] = worker === arguments[0];
  133
+      }
126 134
     });
127 135
   });
128 136
 
7  test/simple/test-cluster-worker-death.js
@@ -30,11 +30,12 @@ else {
30 30
   var seenExit = 0;
31 31
   var seenDeath = 0;
32 32
   var worker = cluster.fork();
33  
-  worker.on('exit', function(statusCode) {
34  
-    assert.equal(statusCode, 42);
  33
+  worker.on('exit', function(exitCode, signalCode) {
  34
+    assert.equal(exitCode, 42);
  35
+    assert.equal(signalCode, null);
35 36
     seenExit++;
36 37
   });
37  
-  cluster.on('death', function(worker_) {
  38
+  cluster.on('exit', function(worker_) {
38 39
     assert.equal(worker_, worker);
39 40
     seenDeath++;
40 41
   });
152  test/simple/test-cluster-worker-exit.js
... ...
@@ -0,0 +1,152 @@
  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
+// test-cluster-worker-exit.js
  24
+// verifies that, when a child process exits (by calling `process.exit(code)`)
  25
+// - the parent receives the proper events in the proper order, no duplicates
  26
+// - the exitCode and signalCode are correct in the 'exit' event
  27
+// - the worker.suicide flag, and worker.state are correct
  28
+// - the worker process actually goes away
  29
+
  30
+var common = require('../common');
  31
+var assert = require('assert');
  32
+var cluster = require('cluster');
  33
+
  34
+var EXIT_CODE = 42;
  35
+
  36
+if (cluster.isWorker) {
  37
+  var http = require('http');
  38
+  var server = http.Server(function() { });
  39
+
  40
+  server.once('listening', function() {
  41
+    process.exit(EXIT_CODE);
  42
+  });
  43
+  server.listen(common.PORT, '127.0.0.1');
  44
+
  45
+} else if (cluster.isMaster) {
  46
+
  47
+  var expected_results = {
  48
+      cluster_emitDisconnect: [1, "the cluster did not emit 'disconnect'"],
  49
+      cluster_emitExit: [1, "the cluster did not emit 'exit'"],
  50
+      cluster_exitCode: [EXIT_CODE, 'the cluster exited w/ incorrect exitCode'],
  51
+      cluster_signalCode: [null, 'the cluster exited w/ incorrect signalCode'],
  52
+      worker_emitDisconnect: [1, "the worker did not emit 'disconnect'"],
  53
+      worker_emitExit: [1, "the worker did not emit 'exit'"],
  54
+      worker_state: ['disconnected', 'the worker state is incorrect'],
  55
+      worker_suicideMode: [false, 'the worker.suicide flag is incorrect'],
  56
+      worker_died: [true, 'the worker is still running'],
  57
+      worker_exitCode: [EXIT_CODE, 'the worker exited w/ incorrect exitCode'],
  58
+      worker_signalCode: [null, 'the worker exited w/ incorrect signalCode']
  59
+  };
  60
+  var results = {
  61
+      cluster_emitDisconnect: 0,
  62
+      cluster_emitExit: 0,
  63
+      worker_emitDisconnect: 0,
  64
+      worker_emitExit: 0
  65
+  };
  66
+
  67
+
  68
+  // start worker
  69
+  var worker = cluster.fork();
  70
+
  71
+  worker.once('listening', function() {
  72
+    // the worker is up and running...
  73
+  });
  74
+
  75
+
  76
+  // Check cluster events
  77
+  cluster.on('disconnect', function() {
  78
+    results.cluster_emitDisconnect += 1;
  79
+  });
  80
+  cluster.on('exit', function(worker) {
  81
+    results.cluster_exitCode = worker.process.exitCode;
  82
+    results.cluster_signalCode = worker.process.signalCode;
  83
+    results.cluster_emitExit += 1;
  84
+    assert.ok(results.cluster_emitDisconnect,
  85
+        "cluster: 'exit' event before 'disconnect' event");
  86
+  });
  87
+
  88
+  // Check worker eventes and properties
  89
+  worker.on('disconnect', function() {
  90
+    results.worker_emitDisconnect += 1;
  91
+    results.worker_suicideMode = worker.suicide;
  92
+    results.worker_state = worker.state;
  93
+  });
  94
+
  95
+  // Check that the worker died
  96
+  worker.once('exit', function(exitCode, signalCode) {
  97
+    results.worker_exitCode = exitCode;
  98
+    results.worker_signalCode = signalCode;
  99
+    results.worker_emitExit += 1;
  100
+    results.worker_died = !alive(worker.process.pid);
  101
+    assert.ok(results.worker_emitDisconnect,
  102
+        "worker: 'exit' event before 'disconnect' event");
  103
+
  104
+    process.nextTick(function() { finish_test(); });
  105
+  });
  106
+
  107
+  var finish_test = function() {
  108
+    try {
  109
+      checkResults(expected_results, results);
  110
+    } catch (exc) {
  111
+      console.error('FAIL: ' + exc.message);
  112
+      if (exc.name != 'AssertionError') {
  113
+        console.trace(exc);
  114
+      }
  115
+
  116
+      process.exit(1);
  117
+      return;
  118
+    }
  119
+    process.exit(0);
  120
+  };
  121
+}
  122
+
  123
+// some helper functions ...
  124
+
  125
+  function checkResults(expected_results, results) {
  126
+    for (var k in expected_results) {
  127
+      var actual = results[k],
  128
+          expected = expected_results[k];
  129
+
  130
+      if (typeof expected === 'function') {
  131
+        expected(r[k]);
  132
+      } else {
  133
+        var msg = (expected[1] || '') +
  134
+            (' [expected: ' + expected[0] + ' / actual: ' + actual + ']');
  135
+
  136
+        if (expected && expected.length) {
  137
+          assert.equal(actual, expected[0], msg);
  138
+        } else {
  139
+          assert.equal(actual, expected, msg);
  140
+        }
  141
+      }
  142
+    }
  143
+  }
  144
+
  145
+  function alive(pid) {
  146
+    try {
  147
+      process.kill(pid, 'SIGCONT');
  148
+      return true;
  149
+    } catch (e) {
  150
+      return false;
  151
+    }
  152
+  }
149  test/simple/test-cluster-worker-kill.js
... ...
@@ -0,0 +1,149 @@
  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
+// test-cluster-worker-kill.js
  24
+// verifies that, when a child process is killed (we use SIGHUP)
  25
+// - the parent receives the proper events in the proper order, no duplicates
  26
+// - the exitCode and signalCode are correct in the 'exit' event
  27
+// - the worker.suicide flag, and worker.state are correct
  28
+// - the worker process actually goes away
  29
+
  30
+var common = require('../common');
  31
+var assert = require('assert');
  32
+var cluster = require('cluster');
  33
+
  34
+if (cluster.isWorker) {
  35
+  var http = require('http');
  36
+  var server = http.Server(function() { });
  37
+
  38
+  server.once('listening', function() { });
  39
+  server.listen(common.PORT, '127.0.0.1');
  40
+
  41
+} else if (cluster.isMaster) {
  42
+
  43
+  var KILL_SIGNAL = 'SIGHUP',
  44
+    expected_results = {
  45
+      cluster_emitDisconnect: [1, "the cluster did not emit 'disconnect'"],
  46
+      cluster_emitExit: [1, "the cluster did not emit 'exit'"],
  47
+      cluster_exitCode: [null, 'the cluster exited w/ incorrect exitCode'],
  48
+      cluster_signalCode: [KILL_SIGNAL, 'the cluster exited w/ incorrect signalCode'],
  49
+      worker_emitDisconnect: [1, "the worker did not emit 'disconnect'"],
  50
+      worker_emitExit: [1, "the worker did not emit 'exit'"],
  51
+      worker_state: ['disconnected', 'the worker state is incorrect'],
  52
+      worker_suicideMode: [false, 'the worker.suicide flag is incorrect'],
  53
+      worker_died: [true, 'the worker is still running'],
  54
+      worker_exitCode: [null, 'the worker exited w/ incorrect exitCode'],
  55
+      worker_signalCode: [KILL_SIGNAL, 'the worker exited w/ incorrect signalCode']
  56
+    },
  57
+    results = {
  58
+      cluster_emitDisconnect: 0,
  59
+      cluster_emitExit: 0,
  60
+      worker_emitDisconnect: 0,
  61
+      worker_emitExit: 0
  62
+    };
  63
+
  64
+
  65
+  // start worker
  66
+  var worker = cluster.fork();
  67
+
  68
+  // when the worker is up and running, kill it
  69
+  worker.once('listening', function() {
  70
+    worker.process.kill(KILL_SIGNAL);
  71
+  });
  72
+
  73
+
  74
+  // Check cluster events
  75
+  cluster.on('disconnect', function() {
  76
+    results.cluster_emitDisconnect += 1;
  77
+  });
  78
+  cluster.on('exit', function(worker) {
  79
+    results.cluster_exitCode = worker.process.exitCode;
  80
+    results.cluster_signalCode = worker.process.signalCode;
  81
+    results.cluster_emitExit += 1;
  82
+    assert.ok(results.cluster_emitDisconnect,
  83
+        "cluster: 'exit' event before 'disconnect' event");
  84
+  });
  85
+
  86
+  // Check worker eventes and properties
  87
+  worker.on('disconnect', function() {
  88
+    results.worker_emitDisconnect += 1;
  89
+    results.worker_suicideMode = worker.suicide;
  90
+    results.worker_state = worker.state;
  91
+  });
  92
+
  93
+  // Check that the worker died
  94
+  worker.once('exit', function(exitCode, signalCode) {
  95
+    results.worker_exitCode = exitCode;
  96
+    results.worker_signalCode = signalCode;
  97
+    results.worker_emitExit += 1;
  98
+    results.worker_died = !alive(worker.process.pid);
  99
+    assert.ok(results.worker_emitDisconnect,
  100
+        "worker: 'exit' event before 'disconnect' event");
  101
+
  102
+    process.nextTick(function() { finish_test(); });
  103
+  });
  104
+
  105
+  var finish_test = function() {
  106
+    try {
  107
+      checkResults(expected_results, results);
  108
+    } catch (exc) {
  109
+      console.error('FAIL: ' + exc.message);
  110
+      if (exc.name != 'AssertionError') {
  111
+        console.trace(exc);
  112
+      }
  113
+
  114
+      process.exit(1);
  115
+      return;
  116
+    }
  117
+    process.exit(0);
  118
+  };
  119
+}
  120
+
  121
+// some helper functions ...
  122
+
  123
+  function checkResults(expected_results, results) {
  124
+    for (var k in expected_results) {
  125
+      var actual = results[k],
  126
+          expected = expected_results[k];
  127
+
  128
+      if (typeof expected === 'function') {
  129
+        expected(r[k]);
  130
+      } else {
  131
+        var msg = (expected[1] || '') +
  132
+            (' [expected: ' + expected[0] + ' / actual: ' + actual + ']');
  133
+        if (expected && expected.length) {
  134
+          assert.equal(actual, expected[0], msg);
  135
+        } else {
  136
+          assert.equal(actual, expected, msg);
  137
+        }
  138
+      }
  139
+    }
  140
+  }
  141
+
  142
+  function alive(pid) {
  143
+    try {
  144
+      process.kill(pid, 'SIGCONT');
  145
+      return true;
  146
+    } catch (e) {
  147
+      return false;
  148
+    }
  149
+  }

0 notes on commit a62dd44

Please sign in to comment.
Something went wrong with that request. Please try again.