diff --git a/lib/async_hooks.js b/lib/async_hooks.js index e1029c97a57eec..f70a2e2080ac69 100644 --- a/lib/async_hooks.js +++ b/lib/async_hooks.js @@ -60,19 +60,13 @@ async_wrap.setupHooks({ init, destroy: emitDestroyN }); // Used to fatally abort the process if a callback throws. -function fatalError(e) { - if (typeof e.stack === 'string') { - process._rawDebug(e.stack); - } else { - const o = { message: e }; - Error.captureStackTrace(o, fatalError); - process._rawDebug(o.stack); - } - if (process.execArgv.some( - (e) => /^--abort[_-]on[_-]uncaught[_-]exception$/.test(e))) { - process.abort(); - } - process.exit(1); +// This is JavaScript version of the ClearFatalExceptionHandlers function +// in C++. +function clearFatalExceptionHandlers(e) { + process.domain = undefined; + // Don't use removeAllListeners in case there is a removeListener listener + // that would revert it. + process._events.uncaughtException = undefined; } @@ -334,7 +328,14 @@ function emitInitS(asyncId, type, triggerAsyncId, resource) { if (!Number.isSafeInteger(triggerAsyncId) || triggerAsyncId < 0) throw new RangeError('triggerAsyncId must be an unsigned integer'); - init(asyncId, type, triggerAsyncId, resource); + // Remove prepear for a fatal exception in case an error occurred + var didThrow = true; + try { + init(asyncId, type, triggerAsyncId, resource); + didThrow = false; + } finally { + if (didThrow) clearFatalExceptionHandlers(); + } // Isn't null if hooks were added/removed while the hooks were running. if (tmp_active_hooks_array !== null) { @@ -345,15 +346,10 @@ function emitInitS(asyncId, type, triggerAsyncId, resource) { function emitBeforeN(asyncId) { processing_hook = true; - // Use a single try/catch for all hook to avoid setting up one per iteration. - try { - for (var i = 0; i < active_hooks_array.length; i++) { - if (typeof active_hooks_array[i][before_symbol] === 'function') { - active_hooks_array[i][before_symbol](asyncId); - } + for (var i = 0; i < active_hooks_array.length; i++) { + if (typeof active_hooks_array[i][before_symbol] === 'function') { + active_hooks_array[i][before_symbol](asyncId); } - } catch (e) { - fatalError(e); } processing_hook = false; @@ -371,15 +367,26 @@ function emitBeforeS(asyncId, triggerAsyncId = asyncId) { // Validate the ids. if (asyncId < 0 || triggerAsyncId < 0) { - fatalError('before(): asyncId or triggerAsyncId is less than zero ' + - `(asyncId: ${asyncId}, triggerAsyncId: ${triggerAsyncId})`); + clearFatalExceptionHandlers(); + throw new Error( + 'before(): asyncId or triggerAsyncId is less than zero ' + + `(asyncId: ${asyncId}, triggerAsyncId: ${triggerAsyncId})` + ); } pushAsyncIds(asyncId, triggerAsyncId); if (async_hook_fields[kBefore] === 0) return; - emitBeforeN(asyncId); + + // Remove prepear for a fatal exception in case an error occurred + var didThrow = true; + try { + emitBeforeN(asyncId); + didThrow = false; + } finally { + if (didThrow) clearFatalExceptionHandlers(); + } } @@ -387,15 +394,10 @@ function emitBeforeS(asyncId, triggerAsyncId = asyncId) { // this is called. function emitAfterN(asyncId) { processing_hook = true; - // Use a single try/catch for all hook to avoid setting up one per iteration. - try { - for (var i = 0; i < active_hooks_array.length; i++) { - if (typeof active_hooks_array[i][after_symbol] === 'function') { - active_hooks_array[i][after_symbol](asyncId); - } + for (var i = 0; i < active_hooks_array.length; i++) { + if (typeof active_hooks_array[i][after_symbol] === 'function') { + active_hooks_array[i][after_symbol](asyncId); } - } catch (e) { - fatalError(e); } processing_hook = false; @@ -409,8 +411,16 @@ function emitAfterN(asyncId) { // kIdStackIndex. But what happens if the user doesn't have both before and // after callbacks. function emitAfterS(asyncId) { - if (async_hook_fields[kAfter] > 0) - emitAfterN(asyncId); + if (async_hook_fields[kAfter] > 0) { + // Remove prepear for a fatal exception in case an error occurred + var didThrow = true; + try { + emitAfterN(asyncId); + didThrow = false; + } finally { + if (didThrow) clearFatalExceptionHandlers(); + } + } popAsyncIds(asyncId); } @@ -427,15 +437,10 @@ function emitDestroyS(asyncId) { function emitDestroyN(asyncId) { processing_hook = true; - // Use a single try/catch for all hook to avoid setting up one per iteration. - try { - for (var i = 0; i < active_hooks_array.length; i++) { - if (typeof active_hooks_array[i][destroy_symbol] === 'function') { - active_hooks_array[i][destroy_symbol](asyncId); - } + for (var i = 0; i < active_hooks_array.length; i++) { + if (typeof active_hooks_array[i][destroy_symbol] === 'function') { + active_hooks_array[i][destroy_symbol](asyncId); } - } catch (e) { - fatalError(e); } processing_hook = false; @@ -460,18 +465,13 @@ function emitDestroyN(asyncId) { // exceptions. function init(asyncId, type, triggerAsyncId, resource) { processing_hook = true; - // Use a single try/catch for all hook to avoid setting up one per iteration. - try { - for (var i = 0; i < active_hooks_array.length; i++) { - if (typeof active_hooks_array[i][init_symbol] === 'function') { - active_hooks_array[i][init_symbol]( - asyncId, type, triggerAsyncId, - resource - ); - } + for (var i = 0; i < active_hooks_array.length; i++) { + if (typeof active_hooks_array[i][init_symbol] === 'function') { + active_hooks_array[i][init_symbol]( + asyncId, type, triggerAsyncId, + resource + ); } - } catch (e) { - fatalError(e); } processing_hook = false; } diff --git a/test/async-hooks/test-callback-error.js b/test/async-hooks/test-callback-error.js index b110e700e61e4c..00b91adfd4fadb 100644 --- a/test/async-hooks/test-callback-error.js +++ b/test/async-hooks/test-callback-error.js @@ -6,6 +6,10 @@ const async_hooks = require('async_hooks'); const initHooks = require('./init-hooks'); const arg = process.argv[2]; +if (arg) { + process.on('uncaughtException', common.mustNotCall()); +} + switch (arg) { case 'test_init_callback': initHooks({ @@ -50,7 +54,7 @@ assert.ok(!arg); console.time('end case 1'); const child = spawnSync(process.execPath, [__filename, 'test_init_callback']); assert.ifError(child.error); - const test_init_first_line = child.stderr.toString().split(/[\r\n]+/g)[0]; + const test_init_first_line = child.stderr.toString().split(/[\r\n]+/g)[3]; assert.strictEqual(test_init_first_line, 'Error: test_init_callback'); assert.strictEqual(child.status, 1); console.timeEnd('end case 1'); @@ -61,7 +65,7 @@ assert.ok(!arg); console.time('end case 2'); const child = spawnSync(process.execPath, [__filename, 'test_callback']); assert.ifError(child.error); - const test_callback_first_line = child.stderr.toString().split(/[\r\n]+/g)[0]; + const test_callback_first_line = child.stderr.toString().split(/[\r\n]+/g)[3]; assert.strictEqual(test_callback_first_line, 'Error: test_callback'); assert.strictEqual(child.status, 1); console.timeEnd('end case 2'); @@ -115,6 +119,6 @@ assert.ok(!arg); assert.strictEqual(stdout, ''); const firstLineStderr = stderr.split(/[\r\n]+/g)[0].trim(); assert.strictEqual(firstLineStderr, 'Error: test_callback_abort'); + console.timeEnd('end case 3'); }); - console.timeEnd('end case 3'); } diff --git a/test/async-hooks/test-emit-before-after.js b/test/async-hooks/test-emit-before-after.js index f4757a28876acc..a0d0d4147706fe 100644 --- a/test/async-hooks/test-emit-before-after.js +++ b/test/async-hooks/test-emit-before-after.js @@ -16,13 +16,13 @@ switch (process.argv[2]) { } const c1 = spawnSync(process.execPath, [__filename, 'test_invalid_async_id']); -assert.strictEqual(c1.stderr.toString().split(/[\r\n]+/g)[0], +assert.strictEqual(c1.stderr.toString().split(/[\r\n]+/g)[3], 'Error: before(): asyncId or triggerAsyncId is less than ' + 'zero (asyncId: -1, triggerAsyncId: -1)'); assert.strictEqual(c1.status, 1); const c2 = spawnSync(process.execPath, [__filename, 'test_invalid_trigger_id']); -assert.strictEqual(c2.stderr.toString().split(/[\r\n]+/g)[0], +assert.strictEqual(c2.stderr.toString().split(/[\r\n]+/g)[3], 'Error: before(): asyncId or triggerAsyncId is less than ' + 'zero (asyncId: 1, triggerAsyncId: -1)'); assert.strictEqual(c2.status, 1);