From 445837851bd8e8395f1de2358fe55fb3b967988d Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Wed, 23 Oct 2019 16:06:16 +0200 Subject: [PATCH] async_hooks: only emit `after` for AsyncResource if stack not empty We clear the async id stack inside the uncaught exception handler and emit `after` events in the process, so we should not emit `after` a second time from the `runInAsyncScope()` code. This should match the behaviour we have in C++. Fixes: https://github.com/nodejs/node/issues/30080 PR-URL: https://github.com/nodejs/node/pull/30087 Reviewed-By: Gus Caplan Reviewed-By: Anto Aravinth --- lib/async_hooks.js | 4 ++- ...est-queue-microtask-uncaught-asynchooks.js | 36 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 test/parallel/test-queue-microtask-uncaught-asynchooks.js 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']); +});