diff --git a/lib/async_hooks.js b/lib/async_hooks.js index 0c177055364e41..c18a902c2ace84 100644 --- a/lib/async_hooks.js +++ b/lib/async_hooks.js @@ -17,6 +17,7 @@ const { executionAsyncId, triggerAsyncId, // Private API + hasAsyncIdStack, getHookArrays, enableHooks, disableHooks, @@ -172,7 +173,8 @@ class AsyncResource { return fn(...args); return Reflect.apply(fn, thisArg, args); } finally { - emitAfter(asyncId); + if (hasAsyncIdStack()) + emitAfter(asyncId); } } diff --git a/test/parallel/test-queue-microtask-uncaught-asynchooks.js b/test/parallel/test-queue-microtask-uncaught-asynchooks.js new file mode 100644 index 00000000000000..ee64c6e68ab7ab --- /dev/null +++ b/test/parallel/test-queue-microtask-uncaught-asynchooks.js @@ -0,0 +1,36 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const async_hooks = require('async_hooks'); + +// Regression test for https://github.com/nodejs/node/issues/30080: +// An uncaught exception inside a queueMicrotask callback should not lead +// to multiple after() calls for it. + +let µtaskId; +const events = []; + +async_hooks.createHook({ + init(id, type, triggerId, resoure) { + if (type === 'Microtask') { + µtaskId = id; + events.push('init'); + } + }, + before(id) { + if (id === µtaskId) events.push('before'); + }, + after(id) { + if (id === µtaskId) events.push('after'); + }, + destroy(id) { + if (id === µtaskId) events.push('destroy'); + } +}).enable(); + +queueMicrotask(() => { throw new Error(); }); + +process.on('uncaughtException', common.mustCall()); +process.on('exit', () => { + assert.deepStrictEqual(events, ['init', 'after', 'before', 'destroy']); +});