Skip to content

Commit 700d576

Browse files
trevnorrisMylesBorins
authored andcommitted
async_hooks: emitAfter correctly on fatalException
Previously calling emitAfter() from _fatalException would skipt the first asyncId. Instead use the size() of the std::stack to determine how many times to loop and call emitAfter(). PR-URL: #14914 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Refael Ackermann <[email protected]>
1 parent 5b13add commit 700d576

File tree

6 files changed

+56
-3
lines changed

6 files changed

+56
-3
lines changed

lib/internal/bootstrap_node.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@
347347
// Arrays containing hook flags and ids for async_hook calls.
348348
const { async_hook_fields, async_uid_fields } = async_wrap;
349349
// Internal functions needed to manipulate the stack.
350-
const { clearIdStack, popAsyncIds } = async_wrap;
350+
const { clearIdStack, asyncIdStackSize } = async_wrap;
351351
const { kAfter, kCurrentAsyncId, kInitTriggerId } = async_wrap.constants;
352352

353353
process._fatalException = function(er) {
@@ -384,8 +384,7 @@
384384
do {
385385
NativeModule.require('async_hooks').emitAfter(
386386
async_uid_fields[kCurrentAsyncId]);
387-
// popAsyncIds() returns true if there are more ids on the stack.
388-
} while (popAsyncIds(async_uid_fields[kCurrentAsyncId]));
387+
} while (asyncIdStackSize() > 0);
389388
// Or completely empty the id stack.
390389
} else {
391390
clearIdStack();

src/async-wrap.cc

+8
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,13 @@ void AsyncWrap::PopAsyncIds(const FunctionCallbackInfo<Value>& args) {
474474
}
475475

476476

477+
void AsyncWrap::AsyncIdStackSize(const FunctionCallbackInfo<Value>& args) {
478+
Environment* env = Environment::GetCurrent(args);
479+
args.GetReturnValue().Set(
480+
static_cast<double>(env->async_hooks()->stack_size()));
481+
}
482+
483+
477484
void AsyncWrap::ClearIdStack(const FunctionCallbackInfo<Value>& args) {
478485
Environment* env = Environment::GetCurrent(args);
479486
env->async_hooks()->clear_id_stack();
@@ -503,6 +510,7 @@ void AsyncWrap::Initialize(Local<Object> target,
503510
env->SetMethod(target, "setupHooks", SetupHooks);
504511
env->SetMethod(target, "pushAsyncIds", PushAsyncIds);
505512
env->SetMethod(target, "popAsyncIds", PopAsyncIds);
513+
env->SetMethod(target, "asyncIdStackSize", AsyncIdStackSize);
506514
env->SetMethod(target, "clearIdStack", ClearIdStack);
507515
env->SetMethod(target, "addIdToDestroyList", QueueDestroyId);
508516
env->SetMethod(target, "enablePromiseHook", EnablePromiseHook);

src/async-wrap.h

+1
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ class AsyncWrap : public BaseObject {
102102
static void GetAsyncId(const v8::FunctionCallbackInfo<v8::Value>& args);
103103
static void PushAsyncIds(const v8::FunctionCallbackInfo<v8::Value>& args);
104104
static void PopAsyncIds(const v8::FunctionCallbackInfo<v8::Value>& args);
105+
static void AsyncIdStackSize(const v8::FunctionCallbackInfo<v8::Value>& args);
105106
static void ClearIdStack(const v8::FunctionCallbackInfo<v8::Value>& args);
106107
static void AsyncReset(const v8::FunctionCallbackInfo<v8::Value>& args);
107108
static void QueueDestroyId(const v8::FunctionCallbackInfo<v8::Value>& args);

src/env-inl.h

+4
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,10 @@ inline bool Environment::AsyncHooks::pop_ids(double async_id) {
166166
return !ids_stack_.empty();
167167
}
168168

169+
inline size_t Environment::AsyncHooks::stack_size() {
170+
return ids_stack_.size();
171+
}
172+
169173
inline void Environment::AsyncHooks::clear_id_stack() {
170174
while (!ids_stack_.empty())
171175
ids_stack_.pop();

src/env.h

+1
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,7 @@ class Environment {
396396

397397
inline void push_ids(double async_id, double trigger_id);
398398
inline bool pop_ids(double async_id);
399+
inline size_t stack_size();
399400
inline void clear_id_stack(); // Used in fatal exceptions.
400401

401402
// Used to propagate the trigger_id to the constructor of any newly created
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const async_hooks = require('async_hooks');
6+
7+
const id_obj = {};
8+
let collect = true;
9+
10+
const hook = async_hooks.createHook({
11+
before(id) { if (collect) id_obj[id] = true; },
12+
after(id) { delete id_obj[id]; },
13+
}).enable();
14+
15+
process.once('uncaughtException', common.mustCall((er) => {
16+
assert.strictEqual(er.message, 'bye');
17+
collect = false;
18+
}));
19+
20+
setImmediate(common.mustCall(() => {
21+
process.nextTick(common.mustCall(() => {
22+
assert.strictEqual(Object.keys(id_obj).length, 0);
23+
hook.disable();
24+
}));
25+
26+
// Create a stack of async ids that will need to be emitted in the case of
27+
// an uncaught exception.
28+
const ar1 = new async_hooks.AsyncResource('Mine');
29+
ar1.emitBefore();
30+
31+
const ar2 = new async_hooks.AsyncResource('Mine');
32+
ar2.emitBefore();
33+
34+
throw new Error('bye');
35+
36+
// TODO(trevnorris): This test shows that the after() hooks are always called
37+
// correctly, but it doesn't solve where the emitDestroy() is missed because
38+
// of the uncaught exception. Simple solution is to always call emitDestroy()
39+
// before the emitAfter(), but how to codify this?
40+
}));

0 commit comments

Comments
 (0)