mirror of https://github.com/lukechilds/node.git
Browse Source
Revertv5.x0af4c9ea74
, parts of921f2de6cf
and port https://github.com/nodejs/node-v0.x-archive/pull/25835 from v0.12 to master so that node aborts at the right time when an error is thrown and --abort-on-uncaught-exception is used. Fixes #3035. PR: #3036 PR-URL: https://github.com/nodejs/node/pull/3036 Reviewed-By: Ben Noordhuis <ben@strongloop.com>
Jeremy Whitlock
9 years ago
committed by
Julien Gilli
8 changed files with 363 additions and 78 deletions
@ -0,0 +1,50 @@ |
|||
'use strict'; |
|||
|
|||
/* |
|||
* The goal of this test is to make sure that when a top-level error |
|||
* handler throws an error following the handling of a previous error, |
|||
* the process reports the error message from the error thrown in the |
|||
* top-level error handler, not the one from the previous error. |
|||
*/ |
|||
|
|||
const common = require('../common'); |
|||
|
|||
const domainErrHandlerExMessage = 'exception from domain error handler'; |
|||
const internalExMessage = 'You should NOT see me'; |
|||
|
|||
if (process.argv[2] === 'child') { |
|||
var domain = require('domain'); |
|||
var d = domain.create(); |
|||
|
|||
d.on('error', function() { |
|||
throw new Error(domainErrHandlerExMessage); |
|||
}); |
|||
|
|||
d.run(function doStuff() { |
|||
process.nextTick(function() { |
|||
throw new Error(internalExMessage); |
|||
}); |
|||
}); |
|||
} else { |
|||
var fork = require('child_process').fork; |
|||
var assert = require('assert'); |
|||
|
|||
var child = fork(process.argv[1], ['child'], {silent:true}); |
|||
var stderrOutput = ''; |
|||
if (child) { |
|||
child.stderr.on('data', function onStderrData(data) { |
|||
stderrOutput += data.toString(); |
|||
}); |
|||
|
|||
child.on('exit', function onChildExited(exitCode, signal) { |
|||
assert(stderrOutput.indexOf(domainErrHandlerExMessage) !== -1); |
|||
assert(stderrOutput.indexOf(internalExMessage) === -1); |
|||
|
|||
var expectedExitCode = 7; |
|||
var expectedSignal = null; |
|||
|
|||
assert.equal(exitCode, expectedExitCode); |
|||
assert.equal(signal, expectedSignal); |
|||
}); |
|||
} |
|||
} |
@ -0,0 +1,220 @@ |
|||
'use strict'; |
|||
|
|||
const assert = require('assert'); |
|||
const fs = require('fs'); |
|||
const common = require('../common'); |
|||
|
|||
/* |
|||
* The goal of this test is to make sure that: |
|||
* |
|||
* - Even if --abort_on_uncaught_exception is passed on the command line, |
|||
* setting up a top-level domain error handler and throwing an error |
|||
* within this domain does *not* make the process abort. The process exits |
|||
* gracefully. |
|||
* |
|||
* - When passing --abort_on_uncaught_exception on the command line and |
|||
* setting up a top-level domain error handler, an error thrown |
|||
* within this domain's error handler *does* make the process abort. |
|||
* |
|||
* - When *not* passing --abort_on_uncaught_exception on the command line and |
|||
* setting up a top-level domain error handler, an error thrown within this |
|||
* domain's error handler does *not* make the process abort, but makes it exit |
|||
* with the proper failure exit code. |
|||
* |
|||
* - When throwing an error within the top-level domain's error handler |
|||
* within a try/catch block, the process should exit gracefully, whether or |
|||
* not --abort_on_uncaught_exception is passed on the command line. |
|||
*/ |
|||
|
|||
const domainErrHandlerExMessage = 'exception from domain error handler'; |
|||
|
|||
if (process.argv[2] === 'child') { |
|||
var domain = require('domain'); |
|||
var d = domain.create(); |
|||
var triggeredProcessUncaughtException = false; |
|||
|
|||
process.on('uncaughtException', function onUncaughtException() { |
|||
// The process' uncaughtException event must not be emitted when
|
|||
// an error handler is setup on the top-level domain.
|
|||
// Exiting with exit code of 42 here so that it would assert when
|
|||
// the parent checks the child exit code.
|
|||
process.exit(42); |
|||
}); |
|||
|
|||
d.on('error', function(err) { |
|||
// Swallowing the error on purpose if 'throwInDomainErrHandler' is not
|
|||
// set
|
|||
if (process.argv.indexOf('throwInDomainErrHandler') !== -1) { |
|||
// If useTryCatch is set, wrap the throw in a try/catch block.
|
|||
// This is to make sure that a caught exception does not trigger
|
|||
// an abort.
|
|||
if (process.argv.indexOf('useTryCatch') !== -1) { |
|||
try { |
|||
throw new Error(domainErrHandlerExMessage); |
|||
} catch (e) { |
|||
} |
|||
} else { |
|||
throw new Error(domainErrHandlerExMessage); |
|||
} |
|||
} |
|||
}); |
|||
|
|||
d.run(function doStuff() { |
|||
// Throwing from within different types of callbacks as each of them
|
|||
// handles domains differently
|
|||
process.nextTick(function() { |
|||
throw new Error('Error from nextTick callback'); |
|||
}); |
|||
|
|||
fs.exists('/non/existing/file', function onExists(exists) { |
|||
throw new Error('Error from fs.exists callback'); |
|||
}); |
|||
|
|||
setImmediate(function onSetImmediate() { |
|||
throw new Error('Error from setImmediate callback'); |
|||
}); |
|||
|
|||
setTimeout(function onTimeout() { |
|||
throw new Error('Error from setTimeout callback'); |
|||
}, 0); |
|||
|
|||
throw new Error('Error from domain.run callback'); |
|||
}); |
|||
} else { |
|||
var exec = require('child_process').exec; |
|||
|
|||
function testDomainExceptionHandling(cmdLineOption, options) { |
|||
if (typeof cmdLineOption === 'object') { |
|||
options = cmdLineOption; |
|||
cmdLineOption = undefined; |
|||
} |
|||
|
|||
var throwInDomainErrHandlerOpt; |
|||
if (options.throwInDomainErrHandler) |
|||
throwInDomainErrHandlerOpt = 'throwInDomainErrHandler'; |
|||
|
|||
var cmdToExec = ''; |
|||
if (process.platform !== 'win32') { |
|||
// Do not create core files, as it can take a lot of disk space on
|
|||
// continuous testing and developers' machines
|
|||
cmdToExec += 'ulimit -c 0 && '; |
|||
} |
|||
|
|||
var useTryCatchOpt; |
|||
if (options.useTryCatch) |
|||
useTryCatchOpt = 'useTryCatch'; |
|||
|
|||
cmdToExec += process.argv[0] + ' '; |
|||
cmdToExec += (cmdLineOption ? cmdLineOption : '') + ' '; |
|||
cmdToExec += process.argv[1] + ' '; |
|||
cmdToExec += [ |
|||
'child', |
|||
throwInDomainErrHandlerOpt, |
|||
useTryCatchOpt |
|||
].join(' '); |
|||
|
|||
var child = exec(cmdToExec); |
|||
|
|||
if (child) { |
|||
var childTriggeredOnUncaughtExceptionHandler = false; |
|||
child.on('message', function onChildMsg(msg) { |
|||
if (msg === 'triggeredProcessUncaughtEx') { |
|||
childTriggeredOnUncaughtExceptionHandler = true; |
|||
} |
|||
}); |
|||
|
|||
child.on('exit', function onChildExited(exitCode, signal) { |
|||
var expectedExitCodes; |
|||
var expectedSignals; |
|||
|
|||
// When throwing errors from the top-level domain error handler
|
|||
// outside of a try/catch block, the process should not exit gracefully
|
|||
if (!options.useTryCatch && options.throwInDomainErrHandler) { |
|||
if (cmdLineOption === '--abort_on_uncaught_exception') { |
|||
// If the top-level domain's error handler throws, and only if
|
|||
// --abort_on_uncaught_exception is passed on the command line,
|
|||
// the process must abort.
|
|||
//
|
|||
// We use an array of values since the actual exit code can differ
|
|||
// across compilers.
|
|||
// Depending on the compiler used, node will exit with either
|
|||
// exit code 132 (SIGILL) or 134 (SIGABRT).
|
|||
expectedExitCodes = [132, 134]; |
|||
|
|||
// On platforms using a non-GNU compiler, base::OS::Abort raises
|
|||
// an illegal instruction signal.
|
|||
// On platforms using a GNU compiler but with KSH being 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 SIGABRT or SIGILL.
|
|||
expectedSignals = ['SIGABRT', 'SIGILL']; |
|||
|
|||
// 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 { |
|||
// By default, uncaught exceptions make node exit with an exit
|
|||
// code of 7.
|
|||
assert.equal(exitCode, 7); |
|||
assert.equal(signal, null); |
|||
} |
|||
} else { |
|||
// If the top-level domain's error handler does not throw,
|
|||
// the process must exit gracefully, whether or not
|
|||
// --abort_on_uncaught_exception was passed on the command line
|
|||
assert.equal(exitCode, 0); |
|||
assert.equal(signal, null); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
|
|||
testDomainExceptionHandling('--abort_on_uncaught_exception', { |
|||
throwInDomainErrHandler: false, |
|||
useTryCatch: false |
|||
}); |
|||
|
|||
testDomainExceptionHandling('--abort_on_uncaught_exception', { |
|||
throwInDomainErrHandler: false, |
|||
useTryCatch: true |
|||
}); |
|||
|
|||
testDomainExceptionHandling('--abort_on_uncaught_exception', { |
|||
throwInDomainErrHandler: true, |
|||
useTryCatch: false |
|||
}); |
|||
|
|||
testDomainExceptionHandling('--abort_on_uncaught_exception', { |
|||
throwInDomainErrHandler: true, |
|||
useTryCatch: true |
|||
}); |
|||
|
|||
testDomainExceptionHandling({ |
|||
throwInDomainErrHandler: false |
|||
}); |
|||
|
|||
testDomainExceptionHandling({ |
|||
throwInDomainErrHandler: false, |
|||
useTryCatch: false |
|||
}); |
|||
|
|||
testDomainExceptionHandling({ |
|||
throwInDomainErrHandler: true, |
|||
useTryCatch: true |
|||
}); |
|||
} |
Loading…
Reference in new issue