diff --git a/lib/internal/bootstrap_node.js b/lib/internal/bootstrap_node.js index 50c630f84b..636055d3a9 100644 --- a/lib/internal/bootstrap_node.js +++ b/lib/internal/bootstrap_node.js @@ -383,7 +383,7 @@ // Arrays containing hook flags and ids for async_hook calls. const { async_hook_fields, async_uid_fields } = async_wrap; // Internal functions needed to manipulate the stack. - const { clearIdStack, popAsyncIds } = async_wrap; + const { clearIdStack, asyncIdStackSize } = async_wrap; const { kAfter, kCurrentAsyncId, kInitTriggerId } = async_wrap.constants; process._fatalException = function(er) { @@ -420,8 +420,7 @@ do { NativeModule.require('async_hooks').emitAfter( async_uid_fields[kCurrentAsyncId]); - // popAsyncIds() returns true if there are more ids on the stack. - } while (popAsyncIds(async_uid_fields[kCurrentAsyncId])); + } while (asyncIdStackSize() > 0); // Or completely empty the id stack. } else { clearIdStack(); diff --git a/src/async-wrap.cc b/src/async-wrap.cc index de6ff2fceb..9c25c51853 100644 --- a/src/async-wrap.cc +++ b/src/async-wrap.cc @@ -450,6 +450,13 @@ void AsyncWrap::PopAsyncIds(const FunctionCallbackInfo& args) { } +void AsyncWrap::AsyncIdStackSize(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + args.GetReturnValue().Set( + static_cast(env->async_hooks()->stack_size())); +} + + void AsyncWrap::ClearIdStack(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); env->async_hooks()->clear_id_stack(); @@ -486,6 +493,7 @@ void AsyncWrap::Initialize(Local target, env->SetMethod(target, "setupHooks", SetupHooks); env->SetMethod(target, "pushAsyncIds", PushAsyncIds); env->SetMethod(target, "popAsyncIds", PopAsyncIds); + env->SetMethod(target, "asyncIdStackSize", AsyncIdStackSize); env->SetMethod(target, "clearIdStack", ClearIdStack); env->SetMethod(target, "addIdToDestroyList", QueueDestroyId); env->SetMethod(target, "enablePromiseHook", EnablePromiseHook); diff --git a/src/async-wrap.h b/src/async-wrap.h index 7b427b0904..2b631234b1 100644 --- a/src/async-wrap.h +++ b/src/async-wrap.h @@ -110,6 +110,7 @@ class AsyncWrap : public BaseObject { static void GetAsyncId(const v8::FunctionCallbackInfo& args); static void PushAsyncIds(const v8::FunctionCallbackInfo& args); static void PopAsyncIds(const v8::FunctionCallbackInfo& args); + static void AsyncIdStackSize(const v8::FunctionCallbackInfo& args); static void ClearIdStack(const v8::FunctionCallbackInfo& args); static void AsyncReset(const v8::FunctionCallbackInfo& args); static void QueueDestroyId(const v8::FunctionCallbackInfo& args); diff --git a/src/env-inl.h b/src/env-inl.h index 842eed4cc8..d31b3602e9 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -166,6 +166,10 @@ inline bool Environment::AsyncHooks::pop_ids(double async_id) { return !ids_stack_.empty(); } +inline size_t Environment::AsyncHooks::stack_size() { + return ids_stack_.size(); +} + inline void Environment::AsyncHooks::clear_id_stack() { while (!ids_stack_.empty()) ids_stack_.pop(); diff --git a/src/env.h b/src/env.h index 8016001c36..02f740cfa3 100644 --- a/src/env.h +++ b/src/env.h @@ -400,6 +400,7 @@ class Environment { inline void push_ids(double async_id, double trigger_id); inline bool pop_ids(double async_id); + inline size_t stack_size(); inline void clear_id_stack(); // Used in fatal exceptions. // Used to propagate the trigger_id to the constructor of any newly created diff --git a/test/parallel/test-emit-after-uncaught-exception.js b/test/parallel/test-emit-after-uncaught-exception.js new file mode 100644 index 0000000000..368099d483 --- /dev/null +++ b/test/parallel/test-emit-after-uncaught-exception.js @@ -0,0 +1,40 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const async_hooks = require('async_hooks'); + +const id_obj = {}; +let collect = true; + +const hook = async_hooks.createHook({ + before(id) { if (collect) id_obj[id] = true; }, + after(id) { delete id_obj[id]; }, +}).enable(); + +process.once('uncaughtException', common.mustCall((er) => { + assert.strictEqual(er.message, 'bye'); + collect = false; +})); + +setImmediate(common.mustCall(() => { + process.nextTick(common.mustCall(() => { + assert.strictEqual(Object.keys(id_obj).length, 0); + hook.disable(); + })); + + // Create a stack of async ids that will need to be emitted in the case of + // an uncaught exception. + const ar1 = new async_hooks.AsyncResource('Mine'); + ar1.emitBefore(); + + const ar2 = new async_hooks.AsyncResource('Mine'); + ar2.emitBefore(); + + throw new Error('bye'); + + // TODO(trevnorris): This test shows that the after() hooks are always called + // correctly, but it doesn't solve where the emitDestroy() is missed because + // of the uncaught exception. Simple solution is to always call emitDestroy() + // before the emitAfter(), but how to codify this? +}));