Browse Source

domains: fix handling of uncaught exceptions

Fix node exiting due to an exception being thrown rather than emitting
an `'uncaughtException'` event on the process object when:
1. no error handler is set on the domain within which an error is thrown
2. an `'uncaughtException'` event listener is set on the process

Also fix an issue where the process would not abort in the proper
function call if an error is thrown within a domain with no error
handler and `--abort-on-uncaught-exception` is used.

Finally, change the behavior of --abort-on-uncaught-exception so that,
if the domain within which the error is thrown has no error handler, but
a domain further up the domains stack has one, the process will not
abort.

Fixes #3607 and #3653.

PR: #3654
PR-URL: https://github.com/nodejs/node/pull/3654
Reviewed-By: Chris Dickinson <chris@neversaw.us>
process-exit-stdio-flushing
Julien Gilli 9 years ago
parent
commit
425a3545d2
  1. 17
      lib/domain.js
  2. 1
      src/env.h
  3. 48
      src/node.cc
  4. 34
      test/common.js
  5. 232
      test/parallel/test-domain-abort-on-uncaught.js
  6. 168
      test/parallel/test-domain-no-error-handler-abort-on-uncaught.js
  7. 101
      test/parallel/test-domain-throw-error-then-throw-from-uncaught-exception-handler.js
  8. 205
      test/parallel/test-domain-uncaught-exception.js
  9. 34
      test/parallel/test-domain-with-abort-on-uncaught-exception.js

17
lib/domain.js

@ -27,8 +27,13 @@ Object.defineProperty(process, 'domain', {
} }
}); });
// It's possible to enter one domain while already inside
// another one. The stack is each entered domain.
const stack = [];
exports._stack = stack;
// let the process know we're using domains // let the process know we're using domains
const _domain_flag = process._setupDomainUse(_domain); const _domain_flag = process._setupDomainUse(_domain, stack);
exports.Domain = Domain; exports.Domain = Domain;
@ -36,10 +41,6 @@ exports.create = exports.createDomain = function() {
return new Domain(); return new Domain();
}; };
// it's possible to enter one domain while already inside
// another one. the stack is each entered domain.
var stack = [];
exports._stack = stack;
// the active domain is always the one that we're currently in. // the active domain is always the one that we're currently in.
exports.active = null; exports.active = null;
@ -96,6 +97,11 @@ Domain.prototype._errorHandler = function errorHandler(er) {
// that these exceptions are caught, and thus would prevent it from // that these exceptions are caught, and thus would prevent it from
// aborting in these cases. // aborting in these cases.
if (stack.length === 1) { if (stack.length === 1) {
// If there's no error handler, do not emit an 'error' event
// as this would throw an error, make the process exit, and thus
// prevent the process 'uncaughtException' event from being emitted
// if a listener is set.
if (EventEmitter.listenerCount(self, 'error') > 0) {
try { try {
// Set the _emittingTopLevelDomainError so that we know that, even // Set the _emittingTopLevelDomainError so that we know that, even
// if technically the top-level domain is still active, it would // if technically the top-level domain is still active, it would
@ -105,6 +111,7 @@ Domain.prototype._errorHandler = function errorHandler(er) {
} finally { } finally {
process._emittingTopLevelDomainError = false; process._emittingTopLevelDomainError = false;
} }
}
} else { } else {
// wrap this in a try/catch so we don't get infinite throwing // wrap this in a try/catch so we don't get infinite throwing
try { try {

1
src/env.h

@ -242,6 +242,7 @@ namespace node {
V(buffer_prototype_object, v8::Object) \ V(buffer_prototype_object, v8::Object) \
V(context, v8::Context) \ V(context, v8::Context) \
V(domain_array, v8::Array) \ V(domain_array, v8::Array) \
V(domains_stack_array, v8::Array) \
V(fs_stats_constructor_function, v8::Function) \ V(fs_stats_constructor_function, v8::Function) \
V(generic_internal_field_template, v8::ObjectTemplate) \ V(generic_internal_field_template, v8::ObjectTemplate) \
V(jsstream_constructor_template, v8::FunctionTemplate) \ V(jsstream_constructor_template, v8::FunctionTemplate) \

48
src/node.cc

@ -951,17 +951,50 @@ void* ArrayBufferAllocator::Allocate(size_t size) {
return malloc(size); return malloc(size);
} }
static bool DomainHasErrorHandler(const Environment* env,
const Local<Object>& domain) {
HandleScope scope(env->isolate());
Local<Value> domain_event_listeners_v = domain->Get(env->events_string());
if (!domain_event_listeners_v->IsObject())
return false;
Local<Object> domain_event_listeners_o =
domain_event_listeners_v.As<Object>();
Local<Value> domain_error_listeners_v =
domain_event_listeners_o->Get(env->error_string());
if (domain_error_listeners_v->IsFunction() ||
(domain_error_listeners_v->IsArray() &&
domain_error_listeners_v.As<Array>()->Length() > 0))
return true;
return false;
}
static bool DomainsStackHasErrorHandler(const Environment* env) {
HandleScope scope(env->isolate());
static bool IsDomainActive(const Environment* env) {
if (!env->using_domains()) if (!env->using_domains())
return false; return false;
Local<Array> domain_array = env->domain_array().As<Array>(); Local<Array> domains_stack_array = env->domains_stack_array().As<Array>();
if (domain_array->Length() == 0) if (domains_stack_array->Length() == 0)
return false; return false;
Local<Value> domain_v = domain_array->Get(0); uint32_t domains_stack_length = domains_stack_array->Length();
return !domain_v->IsNull(); for (uint32_t i = domains_stack_length; i > 0; --i) {
Local<Value> domain_v = domains_stack_array->Get(i - 1);
if (!domain_v->IsObject())
return false;
Local<Object> domain = domain_v.As<Object>();
if (DomainHasErrorHandler(env, domain))
return true;
}
return false;
} }
@ -975,7 +1008,7 @@ static bool ShouldAbortOnUncaughtException(Isolate* isolate) {
bool isEmittingTopLevelDomainError = bool isEmittingTopLevelDomainError =
process_object->Get(emitting_top_level_domain_error_key)->BooleanValue(); process_object->Get(emitting_top_level_domain_error_key)->BooleanValue();
return !IsDomainActive(env) || isEmittingTopLevelDomainError; return isEmittingTopLevelDomainError || !DomainsStackHasErrorHandler(env);
} }
@ -1004,6 +1037,9 @@ void SetupDomainUse(const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsArray()); CHECK(args[0]->IsArray());
env->set_domain_array(args[0].As<Array>()); env->set_domain_array(args[0].As<Array>());
CHECK(args[1]->IsArray());
env->set_domains_stack_array(args[1].As<Array>());
// Do a little housekeeping. // Do a little housekeeping.
env->process_object()->Delete( env->process_object()->Delete(
FIXED_ONE_BYTE_STRING(args.GetIsolate(), "_setupDomainUse")); FIXED_ONE_BYTE_STRING(args.GetIsolate(), "_setupDomainUse"));

34
test/common.js

@ -471,3 +471,37 @@ ArrayStream.prototype.writable = true;
ArrayStream.prototype.pause = function() {}; ArrayStream.prototype.pause = function() {};
ArrayStream.prototype.resume = function() {}; ArrayStream.prototype.resume = function() {};
ArrayStream.prototype.write = function() {}; ArrayStream.prototype.write = function() {};
// Returns true if the exit code "exitCode" and/or signal name "signal"
// represent the exit code and/or signal name of a node process that aborted,
// false otherwise.
exports.nodeProcessAborted = function nodeProcessAborted(exitCode, signal) {
// Depending on the compiler used, node will exit with either
// exit code 132 (SIGILL), 133 (SIGTRAP) or 134 (SIGABRT).
var expectedExitCodes = [132, 133, 134];
// On platforms using KSH as the default shell (like SmartOS),
// when a process aborts, KSH exits with an exit code that is
// greater than 256, and thus the exit code emitted with the 'exit'
// event is null and the signal is set to either SIGILL, SIGTRAP,
// or SIGABRT (depending on the compiler).
const expectedSignals = ['SIGILL', 'SIGTRAP', 'SIGABRT'];
// On Windows, v8's base::OS::Abort triggers an access violation,
// which corresponds to exit code 3221225477 (0xC0000005)
if (process.platform === 'win32')
expectedExitCodes = [3221225477];
// When using --abort-on-uncaught-exception, V8 will use
// base::OS::Abort to terminate the process.
// Depending on the compiler used, the shell or other aspects of
// the platform used to build the node binary, this will actually
// make V8 exit by aborting or by raising a signal. In any case,
// one of them (exit code or signal) needs to be set to one of
// the expected exit codes or signals.
if (signal !== null) {
return expectedSignals.indexOf(signal) > -1;
} else {
return expectedExitCodes.indexOf(exitCode) > -1;
}
};

232
test/parallel/test-domain-abort-on-uncaught.js

@ -1,58 +1,66 @@
'use strict'; 'use strict';
// Flags: --abort_on_uncaught_exception
var common = require('../common');
var assert = require('assert');
var domain = require('domain');
var tests = [
nextTick,
timer,
timerPlusNextTick,
netServer,
firstRun,
];
var errors = 0; // This test makes sure that when using --abort-on-uncaught-exception and
// when throwing an error from within a domain that has an error handler
// setup, the process _does not_ abort.
process.on('exit', function() { const common = require('../common');
assert.equal(errors, tests.length); const assert = require('assert');
}); const domain = require('domain');
const child_process = require('child_process');
tests.forEach(function(test) { test(); }); var errorHandlerCalled = false;
const tests = [
function nextTick() { function nextTick() {
var d = domain.create(); const d = domain.create();
d.once('error', function(err) { d.once('error', function(err) {
errors += 1; errorHandlerCalled = true;
}); });
d.run(function() { d.run(function() {
process.nextTick(function() { process.nextTick(function() {
throw new Error('exceptional!'); throw new Error('exceptional!');
}); });
}); });
} },
function timer() { function timer() {
var d = domain.create(); const d = domain.create();
d.on('error', function(err) { d.on('error', function(err) {
errors += 1; errorHandlerCalled = true;
}); });
d.run(function() { d.run(function() {
setTimeout(function() { setTimeout(function() {
throw new Error('exceptional!'); throw new Error('exceptional!');
}, 33); }, 33);
}); });
} },
function immediate() {
const d = domain.create();
d.on('error', function errorHandler() {
errorHandlerCalled = true;
});
d.run(function() {
setImmediate(function() {
throw new Error('boom!');
});
});
},
function timerPlusNextTick() { function timerPlusNextTick() {
var d = domain.create(); const d = domain.create();
d.on('error', function(err) { d.on('error', function(err) {
errors += 1; errorHandlerCalled = true;
}); });
d.run(function() { d.run(function() {
setTimeout(function() { setTimeout(function() {
process.nextTick(function() { process.nextTick(function() {
@ -60,32 +68,49 @@ function timerPlusNextTick() {
}); });
}, 33); }, 33);
}); });
} },
function firstRun() { function firstRun() {
var d = domain.create(); const d = domain.create();
d.on('error', function(err) { d.on('error', function(err) {
errors += 1; errorHandlerCalled = true;
}); });
d.run(function() { d.run(function() {
throw new Error('exceptional!'); throw new Error('exceptional!');
}); });
} },
function fsAsync() {
const d = domain.create();
d.on('error', function errorHandler() {
errorHandlerCalled = true;
});
d.run(function() {
var fs = require('fs');
fs.exists('/non/existing/file', function onExists(exists) {
throw new Error('boom!');
});
});
},
function netServer() { function netServer() {
var net = require('net'); const net = require('net');
var d = domain.create(); const d = domain.create();
d.on('error', function(err) { d.on('error', function(err) {
errors += 1; errorHandlerCalled = true;
}); });
d.run(function() { d.run(function() {
var server = net.createServer(function(conn) { const server = net.createServer(function(conn) {
conn.pipe(conn); conn.pipe(conn);
}); });
server.listen(common.PORT, '0.0.0.0', function() { server.listen(common.PORT, common.localhostIPv4, function() {
var conn = net.connect(common.PORT, '0.0.0.0'); const conn = net.connect(common.PORT, common.localhostIPv4);
conn.once('data', function() { conn.once('data', function() {
throw new Error('ok'); throw new Error('ok');
}); });
@ -93,4 +118,139 @@ function netServer() {
server.close(); server.close();
}); });
}); });
},
function firstRunOnlyTopLevelErrorHandler() {
const d = domain.create();
const d2 = domain.create();
d.on('error', function errorHandler() {
errorHandlerCalled = true;
});
d.run(function() {
d2.run(function() {
throw new Error('boom!');
});
});
},
function firstRunNestedWithErrorHandler() {
const d = domain.create();
const d2 = domain.create();
d2.on('error', function errorHandler() {
errorHandlerCalled = true;
});
d.run(function() {
d2.run(function() {
throw new Error('boom!');
});
});
},
function timeoutNestedWithErrorHandler() {
const d = domain.create();
const d2 = domain.create();
d2.on('error', function errorHandler() {
errorHandlerCalled = true;
});
d.run(function() {
d2.run(function() {
setTimeout(function() {
console.log('foo');
throw new Error('boom!');
}, 33);
});
});
},
function setImmediateNestedWithErrorHandler() {
const d = domain.create();
const d2 = domain.create();
d2.on('error', function errorHandler() {
errorHandlerCalled = true;
});
d.run(function() {
d2.run(function() {
setImmediate(function() {
throw new Error('boom!');
});
});
});
},
function nextTickNestedWithErrorHandler() {
const d = domain.create();
const d2 = domain.create();
d2.on('error', function errorHandler() {
errorHandlerCalled = true;
});
d.run(function() {
d2.run(function() {
process.nextTick(function() {
throw new Error('boom!');
});
});
});
},
function fsAsyncNestedWithErrorHandler() {
const d = domain.create();
const d2 = domain.create();
d2.on('error', function errorHandler() {
errorHandlerCalled = true;
});
d.run(function() {
d2.run(function() {
var fs = require('fs');
fs.exists('/non/existing/file', function onExists(exists) {
throw new Error('boom!');
});
});
});
}
];
if (process.argv[2] === 'child') {
const testIndex = +process.argv[3];
tests[testIndex]();
process.on('exit', function onExit() {
assert.equal(errorHandlerCalled, true);
});
} else {
tests.forEach(function(test, testIndex) {
var testCmd = '';
if (process.platform !== 'win32') {
// Do not create core files, as it can take a lot of disk space on
// continuous testing and developers' machines
testCmd += 'ulimit -c 0 && ';
}
testCmd += process.argv[0];
testCmd += ' ' + '--abort-on-uncaught-exception';
testCmd += ' ' + process.argv[1];
testCmd += ' ' + 'child';
testCmd += ' ' + testIndex;
var child = child_process.exec(testCmd);
child.on('exit', function onExit(code, signal) {
assert.equal(code, 0, 'Test at index ' + testIndex +
' should have exited with exit code 0 but instead exited with code ' +
code + ' and signal ' + signal);
});
});
} }

168
test/parallel/test-domain-no-error-handler-abort-on-uncaught.js

@ -0,0 +1,168 @@
'use strict';
/*
* This test makes sure that when using --abort-on-uncaught-exception and
* when throwing an error from within a domain that does not have an error
* handler setup, the process aborts.
*/
const common = require('../common');
const assert = require('assert');
const domain = require('domain');
const child_process = require('child_process');
const tests = [
function() {
const d = domain.create();
d.run(function() {
throw new Error('boom!');
});
},
function() {
const d = domain.create();
const d2 = domain.create();
d.run(function() {
d2.run(function() {
throw new Error('boom!');
});
});
},
function() {
const d = domain.create();
d.run(function() {
setTimeout(function() {
throw new Error('boom!');
});
});
},
function() {
const d = domain.create();
d.run(function() {
setImmediate(function() {
throw new Error('boom!');
});
});
},
function() {
const d = domain.create();
d.run(function() {
process.nextTick(function() {
throw new Error('boom!');
});
});
},
function() {
const d = domain.create();
d.run(function() {
var fs = require('fs');
fs.exists('/non/existing/file', function onExists(exists) {
throw new Error('boom!');
});
});
},
function() {
const d = domain.create();
const d2 = domain.create();
d.on('error', function errorHandler() {
});
d.run(function() {
d2.run(function() {
setTimeout(function() {
throw new Error('boom!');
});
});
});
},
function() {
const d = domain.create();
const d2 = domain.create();
d.on('error', function errorHandler() {
});
d.run(function() {
d2.run(function() {
setImmediate(function() {
throw new Error('boom!');
});
});
});
},
function() {
const d = domain.create();
const d2 = domain.create();
d.on('error', function errorHandler() {
});
d.run(function() {
d2.run(function() {
process.nextTick(function() {
throw new Error('boom!');
});
});
});
},
function() {
const d = domain.create();
const d2 = domain.create();
d.on('error', function errorHandler() {
});
d.run(function() {
d2.run(function() {
var fs = require('fs');
fs.exists('/non/existing/file', function onExists(exists) {
throw new Error('boom!');
});
});
});
},
];
if (process.argv[2] === 'child') {
const testIndex = +process.argv[3];
tests[testIndex]();
} else {
tests.forEach(function(test, testIndex) {
var testCmd = '';
if (process.platform !== 'win32') {
// Do not create core files, as it can take a lot of disk space on
// continuous testing and developers' machines
testCmd += 'ulimit -c 0 && ';
}
testCmd += process.argv[0];
testCmd += ' ' + '--abort-on-uncaught-exception';
testCmd += ' ' + process.argv[1];
testCmd += ' ' + 'child';
testCmd += ' ' + testIndex;
var child = child_process.exec(testCmd);
child.on('exit', function onExit(exitCode, signal) {
const errMsg = 'Test at index ' + testIndex + ' should have aborted ' +
'but instead exited with exit code ' + exitCode + ' and signal ' +
signal;
assert(common.nodeProcessAborted(exitCode, signal), errMsg);
});
});
}

101
test/parallel/test-domain-throw-error-then-throw-from-uncaught-exception-handler.js

@ -0,0 +1,101 @@
'use strict';
// This test makes sure that when throwing an error from a domain, and then
// handling that error in an uncaughtException handler by throwing an error
// again, the exit code, signal and error messages are the ones we expect with
// and without using --abort-on-uncaught-exception.
const common = require('../common');
const assert = require('assert');
const child_process = require('child_process');
const domain = require('domain');
const uncaughtExceptionHandlerErrMsg = 'boom from uncaughtException handler';
const domainErrMsg = 'boom from domain';
const RAN_UNCAUGHT_EXCEPTION_HANDLER_EXIT_CODE = 42;
if (process.argv[2] === 'child') {
process.on('uncaughtException', common.mustCall(function onUncaught() {
if (process.execArgv.indexOf('--abort-on-uncaught-exception') !== -1) {
// When passing --abort-on-uncaught-exception to the child process,
// we want to make sure that this handler (the process' uncaughtException
// event handler) wasn't called. Unfortunately we can't parse the child
// process' output to do that, since on Windows the standard error output
// is not properly flushed in V8's Isolate::Throw right before the
// process aborts due to an uncaught exception, and thus the error
// message representing the error that was thrown cannot be read by the
// parent process. So instead of parsing the child process' stdandard
// error, the parent process will check that in the case
// --abort-on-uncaught-exception was passed, the process did not exit
// with exit code RAN_UNCAUGHT_EXCEPTION_HANDLER_EXIT_CODE.
process.exit(RAN_UNCAUGHT_EXCEPTION_HANDLER_EXIT_CODE);
} else {
// On the other hand, when not passing --abort-on-uncaught-exception to
// the node process, we want to throw in this event handler to make sure
// that the proper error message, exit code and signal are the ones we
// expect.
throw new Error(uncaughtExceptionHandlerErrMsg);
}
}));
const d = domain.create();
d.run(common.mustCall(function() {
throw new Error(domainErrMsg);
}));
} else {
runTestWithoutAbortOnUncaughtException();
runTestWithAbortOnUncaughtException();
}
function runTestWithoutAbortOnUncaughtException() {
child_process.exec(createTestCmdLine(),
function onTestDone(err, stdout, stderr) {
// When _not_ passing --abort-on-uncaught-exception, the process'
// uncaughtException handler _must_ be called, and thus the error
// message must include only the message of the error thrown from the
// process' uncaughtException handler.
assert(stderr.includes(uncaughtExceptionHandlerErrMsg),
'stderr output must include proper uncaughtException handler\'s ' +
'error\'s message');
assert(!stderr.includes(domainErrMsg), 'stderr output must not ' +
'include domain\'s error\'s message');
assert.notEqual(err.code, 0,
'child process should have exited with a non-zero exit code, ' +
'but did not');
});
}
function runTestWithAbortOnUncaughtException() {
child_process.exec(createTestCmdLine({
withAbortOnUncaughtException: true
}), function onTestDone(err, stdout, stderr) {
assert.notEqual(err.code, RAN_UNCAUGHT_EXCEPTION_HANDLER_EXIT_CODE,
'child process should not have run its uncaughtException event ' +
'handler');
assert(common.nodeProcessAborted(err.code, err.signal),
'process should have aborted, but did not');
});
}
function createTestCmdLine(options) {
var testCmd = '';
if (process.platform !== 'win32') {
// Do not create core files, as it can take a lot of disk space on
// continuous testing and developers' machines
testCmd += 'ulimit -c 0 && ';
}
testCmd += process.argv[0];
if (options && options.withAbortOnUncaughtException) {
testCmd += ' ' + '--abort-on-uncaught-exception';
}
testCmd += ' ' + process.argv[1];
testCmd += ' ' + 'child';
return testCmd;
}

205
test/parallel/test-domain-uncaught-exception.js

@ -0,0 +1,205 @@
'use strict';
/*
* The goal of this test is to make sure that errors thrown within domains
* are handled correctly. It checks that the process' 'uncaughtException' event
* is emitted when appropriate, and not emitted when it shouldn't. It also
* checks that the proper domain error handlers are called when they should
* be called, and not called when they shouldn't.
*/
const common = require('../common');
const assert = require('assert');
const domain = require('domain');
const child_process = require('child_process');
const uncaughtExceptions = {};
const tests = [];
function test1() {
/*
* Throwing from an async callback from within a domain that doesn't have
* an error handler must result in emitting the process' uncaughtException
* event.
*/
const d = domain.create();
d.run(function() {
setTimeout(function onTimeout() {
throw new Error('boom!');
});
});
}
tests.push({
fn: test1,
expectedMessages: ['uncaughtException']
});
function test2() {
/*
* Throwing from from within a domain that doesn't have an error handler must
* result in emitting the process' uncaughtException event.
*/
const d2 = domain.create();
d2.run(function() {
throw new Error('boom!');
});
}
tests.push({
fn: test2,
expectedMessages: ['uncaughtException']
});
function test3() {
/*
* This test creates two nested domains: d3 and d4. d4 doesn't register an
* error handler, but d3 does. The error is handled by the d3 domain and thus
* an 'uncaughtException' event should _not_ be emitted.
*/
const d3 = domain.create();
const d4 = domain.create();
d3.on('error', function onErrorInD3Domain(err) {
process.send('errorHandledByDomain');
});
d3.run(function() {
d4.run(function() {
throw new Error('boom!');
});
});
}
tests.push({
fn: test3,
expectedMessages: ['errorHandledByDomain']
});
function test4() {
/*
* This test creates two nested domains: d5 and d6. d6 doesn't register an
* error handler. When the timer's callback is called, because async
* operations like timer callbacks are bound to the domain that was active
* at the time of their creation, and because both d5 and d6 domains have
* exited by the time the timer's callback is called, its callback runs with
* only d6 on the domains stack. Since d6 doesn't register an error handler,
* the process' uncaughtException event should be emitted.
*/
const d5 = domain.create();
const d6 = domain.create();
d5.on('error', function onErrorInD2Domain(err) {
process.send('errorHandledByDomain');
});
d5.run(function() {
d6.run(function() {
setTimeout(function onTimeout() {
throw new Error('boom!');
});
});
});
}
tests.push({
fn: test4,
expectedMessages: ['uncaughtException']
});
function test5() {
/*
* This test creates two nested domains: d7 and d8. d8 _does_ register an
* error handler, so throwing within that domain should not emit an uncaught
* exception.
*/
const d7 = domain.create();
const d8 = domain.create();
d8.on('error', function onErrorInD3Domain(err) {
process.send('errorHandledByDomain');
});
d7.run(function() {
d8.run(function() {
throw new Error('boom!');
});
});
}
tests.push({
fn: test5,
expectedMessages: ['errorHandledByDomain']
});
function test6() {
/*
* This test creates two nested domains: d9 and d10. d10 _does_ register an
* error handler, so throwing within that domain in an async callback should
* _not_ emit an uncaught exception.
*/
const d9 = domain.create();
const d10 = domain.create();
d10.on('error', function onErrorInD2Domain(err) {
process.send('errorHandledByDomain');
});
d9.run(function() {
d10.run(function() {
setTimeout(function onTimeout() {
throw new Error('boom!');
});
});
});
}
tests.push({
fn: test6,
expectedMessages: ['errorHandledByDomain']
});
if (process.argv[2] === 'child') {
const testIndex = process.argv[3];
process.on('uncaughtException', function onUncaughtException() {
process.send('uncaughtException');
});
tests[testIndex].fn();
} else {
// Run each test's function in a child process. Listen on
// messages sent by each child process and compare expected
// messages defined for each test with the actual received messages.
tests.forEach(function doTest(test, testIndex) {
const testProcess = child_process.fork(__filename, ['child', testIndex]);
testProcess.on('message', function onMsg(msg) {
if (test.messagesReceived === undefined)
test.messagesReceived = [];
test.messagesReceived.push(msg);
});
testProcess.on('disconnect', common.mustCall(function onExit() {
// Make sure that all expected messages were sent from the
// child process
test.expectedMessages.forEach(function(expectedMessage) {
if (test.messagesReceived === undefined ||
test.messagesReceived.indexOf(expectedMessage) === -1)
assert(false, 'test ' + test.fn.name +
' should have sent message: ' + expectedMessage +
' but didn\'t');
});
if (test.messagesReceived) {
test.messagesReceived.forEach(function(receivedMessage) {
if (test.expectedMessages.indexOf(receivedMessage) === -1) {
assert(false, 'test ' + test.fn.name +
' should not have sent message: ' + receivedMessage +
' but did');
}
});
}
}));
});
}

34
test/parallel/test-domain-with-abort-on-uncaught-exception.js

@ -131,38 +131,8 @@ if (process.argv[2] === 'child') {
// outside of a try/catch block, the process should not exit gracefully // outside of a try/catch block, the process should not exit gracefully
if (!options.useTryCatch && options.throwInDomainErrHandler) { if (!options.useTryCatch && options.throwInDomainErrHandler) {
if (cmdLineOption === '--abort_on_uncaught_exception') { if (cmdLineOption === '--abort_on_uncaught_exception') {
// If the top-level domain's error handler throws, and only if assert(common.nodeProcessAborted(exitCode, signal),
// --abort_on_uncaught_exception is passed on the command line, 'process should have aborted, but did not');
// the process must abort.
//
// Depending on the compiler used, node will exit with either
// exit code 132 (SIGILL), 133 (SIGTRAP) or 134 (SIGABRT).
expectedExitCodes = [132, 133, 134];
// On platforms using KSH as the default shell (like SmartOS),
// when a process aborts, KSH exits with an exit code that is
// greater than 256, and thus the exit code emitted with the 'exit'
// event is null and the signal is set to either SIGILL, SIGTRAP,
// or SIGABRT (depending on the compiler).
expectedSignals = ['SIGILL', 'SIGTRAP', 'SIGABRT'];
// On Windows, v8's base::OS::Abort triggers an access violation,
// which corresponds to exit code 3221225477 (0xC0000005)
if (process.platform === 'win32')
expectedExitCodes = [3221225477];
// When using --abort-on-uncaught-exception, V8 will use
// base::OS::Abort to terminate the process.
// Depending on the compiler used, the shell or other aspects of
// the platform used to build the node binary, this will actually
// make V8 exit by aborting or by raising a signal. In any case,
// one of them (exit code or signal) needs to be set to one of
// the expected exit codes or signals.
if (signal !== null) {
assert.ok(expectedSignals.indexOf(signal) > -1);
} else {
assert.ok(expectedExitCodes.indexOf(exitCode) > -1);
}
} else { } else {
// By default, uncaught exceptions make node exit with an exit // By default, uncaught exceptions make node exit with an exit
// code of 7. // code of 7.

Loading…
Cancel
Save