public
Last active

Tests the response of node.js child processes to various posix signals.

  • Download Gist
test-child-process-signals.js
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
var child_process = require('child_process'),
assert = require('assert')
 
var isChild = !!(process.send),
isMaster = ((!isChild) && (process.argv.length > 2)),
isTopLevel = (!isMaster && !isChild)
 
 
if( isTopLevel ) {
 
//**
//* TopLevel - The program was called with no arguments. We'll run the test
//* once for each stop-signal ('T' or 'A') in the list. (except, we'll skip
//* SIGUSR1, because node's debugger owns that one.
//*
(function top_level(){
 
var all_signals = [
// A list of all posix-defined signals. "The following signals shall be supported on
// all implementations".
// See: <http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/signal.h.html>
//
// the `action` field shows 'default action', which the OS will (should) take unless
// the signal is trapped and handled differently.
// T = Abnormal termination of the process.
// A = Abnormal termination of the process (with additional actions).
// I = Ignore the signal
// S = Stop the process
// C = Continue the process, if it is stopped; otherwise, ignore the signal.
//
{ name: 'SIGABRT', action: 'A', desc: 'Process abort signal.' },
{ name: 'SIGALRM', action: 'T', desc: 'Alarm clock.' },
{ name: 'SIGBUS', action: 'A', desc: 'Access to an undefined portion of a memory object.' },
{ name: 'SIGCHLD', action: 'I', desc: 'Child process terminated, stopped, or continued. ' },
{ name: 'SIGCONT', action: 'C', desc: 'Continue executing, if stopped.' },
{ name: 'SIGFPE', action: 'A', desc: 'Erroneous arithmetic operation.' },
{ name: 'SIGHUP', action: 'T', desc: 'Hangup.' },
{ name: 'SIGILL', action: 'A', desc: 'Illegal instruction.' },
{ name: 'SIGINT', action: 'T', desc: 'Terminal interrupt signal.' },
{ name: 'SIGKILL', action: 'T', desc: 'Kill (cannot be caught or ignored).' },
{ name: 'SIGPIPE', action: 'T', desc: 'Write on a pipe with no one to read it.' },
{ name: 'SIGQUIT', action: 'A', desc: 'Terminal quit signal.' },
{ name: 'SIGSEGV', action: 'A', desc: 'Invalid memory reference.' },
{ name: 'SIGSTOP', action: 'S', desc: 'Stop executing (cannot be caught or ignored).' },
{ name: 'SIGTERM', action: 'T', desc: 'Termination signal.' },
{ name: 'SIGTSTP', action: 'S', desc: 'Terminal stop signal.' },
{ name: 'SIGTTIN', action: 'S', desc: 'Background process attempting read.' },
{ name: 'SIGTTOU', action: 'S', desc: 'Background process attempting write.' },
{ name: 'SIGUSR1', action: 'T', desc: 'User-defined signal 1.' },
{ name: 'SIGUSR2', action: 'T', desc: 'User-defined signal 2.' },
{ name: 'SIGPOLL', action: 'T', desc: 'Pollable event. ' },
{ name: 'SIGPROF', action: 'T', desc: 'Profiling timer expired. ' },
{ name: 'SIGSYS', action: 'A', desc: 'Bad system call.' },
{ name: 'SIGTRAP', action: 'A', desc: 'Trace/breakpoint trap. ' },
{ name: 'SIGURG', action: 'I', desc: 'High bandwidth data is available at a socket.' },
{ name: 'SIGVTALRM', action: 'T', desc: 'Virtual timer expired.' },
{ name: 'SIGXCPU', action: 'A', desc: 'CPU time limit exceeded.' },
{ name: 'SIGXFSZ', action: 'A', desc: 'File size limit exceeded. ' }
];
 
var selectedActions = {T:true, A:true},
globalResult = 0;
 
function finish() {
process.exit(globalResult);
}
 
(function testSignal(idx) {
idx = idx||0;
 
var sig = all_signals[idx];
if( !sig ) { finish(); return; }
 
function nextTest() {
process.nextTick(function() { testSignal(idx+1); });
};
 
// ignore signals that do not match the "selected actions"
if( ! (sig.action in selectedActions) ) {
nextTest();
return;
}
 
// run the test in its own process.
// the argument `sig.name` tells the script to be a "master"
// and test the given signal. (see "Master" below).
child_process.execFile(
process.execPath, [process.argv[1], sig.name], null,
function(code, stdout, stderr) {
process.stdout.write(stdout);
process.stderr.write(stderr);
 
globalResult = globalResult || code;
nextTest()
}
)
}());
}()); //end: toplevel
 
} else if( isMaster ) {
 
//**
//* Master (Test Stop Signal) - The first cmdline argument will be the
//* name of the signal to be tested. Test it, and exit.
//*
(function do_master() {
 
var excludedSignals = { 'SIGUSR1': 'used by v8/node debugger' },
testSig = process.argv[2],
tmr0 = null,
timeout = 5000,
reportText;
 
function clean_exit(code) {
process.removeAllListeners();
clearTimeout(tmr0);
process.exit(code);
}
 
process.on('uncaughtException', function (err) {
var msg = '';
if( err.name === 'AssertionError' ) {
msg = err.message;
} else {
//throw err;
msg = err;
}
if( reportText ) { msg = reportText + msg; }
print('FAIL: '+ msg + '\n');
clean_exit(1);
});
 
if( testSig ) {
print(testSig + ' ...');
} else {
// disable the timeout if no signal was specified...
timeout = 0;
}
 
if( testSig && (testSig in excludedSignals) ) {
// skip "excluded" signals (treat them as ok)
print( 'OK (skipped) ' + excludedSignals[testSig] + '\n');
clean_exit(0);
 
} else {
 
// -- run the test -- //
 
var didExit = false
, didDisconnect = false
 
if( timeout ) {
tmr0 = setTimeout( function() {
assert.ok( false, 'timeout ('+(timeout/1000)+'s): child did not respond to signal.');
}, timeout);
}
 
child = child_process.fork(process.argv[1]);
 
// child messages us, once it's up and running
child.on('message', function(msg) {
process.nextTick( start );
});
 
// performs the action that was requested of this test run.
// (ie. send a signal, tell child exit, or just wait)
function start() {
if( testSig ) {
child.kill(testSig);
} else {
print('master ['+process.pid+']\n');
print('child ['+child.pid+']\n');
print('waiting for child to exit... ');
}
}
 
child.on('disconnect', function() {
didDisconnect = true;
});
 
child.on('exit', function(exitCode, sigCode) {
clearTimeout(tmr0);
didExit = true;
 
reportText = '[exitCode='+exitCode+', signal='+sigCode+'] ';
//print(reportText);
 
if( testSig ) {
// verify that we got the signal we were expecting...
assert.equal( sigCode, testSig, 'exit: reported incorrect signal');
assert.equal( exitCode , null, 'exit: reported incorrect exitCode.')
} else {
assert( (exitCode != null && sigCode==null) || (exitCode == null && sigCode != null));
}
assert.ok( didDisconnect, '(exit) missing disconnect event, or events emitted out-of-order.');
});
 
process.on('exit', function() {
assert.ok( didExit, 'no \'exit\' event was fired for child process' );
assert.ok( didDisconnect , 'no \'disconnect\' event was fired for child process' );
print('OK\n');
});
 
process.on('SIGINT', function() {
print('interrupted!\n');
clean_exit(-1);
});
}
}()); //end: master
 
} else if( isChild ) {
 
//**
//* Child - created by the master, to be the recipient of the signal
//*
(function do_child() {
 
process.send('helo');
 
// child stays alive, because of ipc channel.
}()); //end: child
 
} else {
// should not happen.
assert.ok( false, "the test script is unable to identify whether it is "+
"master, child, or toplevel. This should never happen");
}
 
// a few shared utilities
function print(str) {
process.stderr.write(str);
}

The following results were obtained using node v0.7.9-pre (6f82b9f), on OS-X lion.

$ node test-child-process-signals.js 
SIGABRT ...OK
SIGALRM ...OK
SIGBUS ...OK
SIGFPE ...OK
SIGHUP ...OK
SIGILL ...OK
SIGINT ...FAIL: [exitCode=1, signal=null] exit: reported incorrect signal
SIGKILL ...OK
SIGPIPE ...FAIL: timeout (5s): child did not respond to signal.
SIGQUIT ...OK
SIGSEGV ...OK
SIGTERM ...FAIL: [exitCode=1, signal=null] exit: reported incorrect signal
SIGUSR1 ...OK (skipped) used by v8/node debugger
SIGUSR2 ...OK
SIGPOLL ...FAIL: Error: Unknown signal: SIGPOLL
SIGPROF ...OK
SIGSYS ...OK
SIGTRAP ...OK
SIGVTALRM ...OK
SIGXCPU ...OK
SIGXFSZ ...OK

Analysis:

  1. we intentionally skipped SIGUSR1 because we know that node's debugger traps this one
  2. SIGPOLL is undefined on OS-X. Darwin's headers define SIGEMT in its place. (looks like -D_POSIX_C_SOURCE might fix this, but that seems to break lots of other stuff).
  3. SIGPIPE is trapped by nodejs (src/node.cc:2723). I assume this is due to "The special problem of SIGPIPE" (see: deps/uv/src/unix/ev/ev.3).
  4. SIGINT, and SIGTERM are trapped by nodejs, and their handler (src/node.cc:2277), calls _exit(1). This causes the child_process to exit "normally" (with a non-zero exit code), rather than exiting "abnormally" (with a signal code).

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.