'use strict'; const common = require('../common'); const assert = require('assert'); const fs = require('fs'); /* * 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') { const domain = require('domain'); const d = domain.create(); 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.includes('throwInDomainErrHandler')) { // 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.includes('useTryCatch')) { try { throw new Error(domainErrHandlerExMessage); } catch { } } 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 { const exec = require('child_process').exec; function testDomainExceptionHandling(cmdLineOption, options) { if (typeof cmdLineOption === 'object') { options = cmdLineOption; cmdLineOption = undefined; } let throwInDomainErrHandlerOpt; if (options.throwInDomainErrHandler) throwInDomainErrHandlerOpt = 'throwInDomainErrHandler'; let cmdToExec = ''; if (!common.isWindows) { // Do not create core files, as it can take a lot of disk space on // continuous testing and developers' machines cmdToExec += 'ulimit -c 0 && '; } let useTryCatchOpt; if (options.useTryCatch) useTryCatchOpt = 'useTryCatch'; cmdToExec += `"${process.argv[0]}" ${cmdLineOption ? cmdLineOption : ''} "${ process.argv[1]}" child ${throwInDomainErrHandlerOpt} ${useTryCatchOpt}`; const child = exec(cmdToExec); if (child) { child.on('exit', function onChildExited(exitCode, signal) { // 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') { assert(common.nodeProcessAborted(exitCode, signal), 'process should have aborted, but did not'); } else { // By default, uncaught exceptions make node exit with an exit // code of 7. assert.strictEqual(exitCode, 7); assert.strictEqual(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.strictEqual(exitCode, 0); assert.strictEqual(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 }); }