Skip to content

Commit a6023ee

Browse files
addaleaxjasnell
authored andcommitted
async_wrap: fix Promises with later enabled hooks
Assign a `PromiseWrap` instance to Promises that do not have one yet when the PromiseHook is being called. Fixes: #13237 PR-URL: #13242 Reviewed-By: Andreas Madsen <amwebdk@gmail.com> Reviewed-By: Matthew Loring <mattloring@google.com>
1 parent 4a8ea63 commit a6023ee

File tree

3 files changed

+49
-10
lines changed

3 files changed

+49
-10
lines changed

src/async-wrap.cc

+12-8
Original file line numberDiff line numberDiff line change
@@ -283,8 +283,8 @@ bool AsyncWrap::EmitAfter(Environment* env, double async_id) {
283283

284284
class PromiseWrap : public AsyncWrap {
285285
public:
286-
PromiseWrap(Environment* env, Local<Object> object)
287-
: AsyncWrap(env, object, PROVIDER_PROMISE) {}
286+
PromiseWrap(Environment* env, Local<Object> object, bool silent)
287+
: AsyncWrap(env, object, PROVIDER_PROMISE, silent) {}
288288
size_t self_size() const override { return sizeof(*this); }
289289
};
290290

@@ -293,13 +293,14 @@ static void PromiseHook(PromiseHookType type, Local<Promise> promise,
293293
Local<Value> parent, void* arg) {
294294
Local<Context> context = promise->CreationContext();
295295
Environment* env = Environment::GetCurrent(context);
296-
if (type == PromiseHookType::kInit) {
297-
PromiseWrap* wrap = new PromiseWrap(env, promise);
296+
PromiseWrap* wrap = Unwrap<PromiseWrap>(promise);
297+
if (type == PromiseHookType::kInit || wrap == nullptr) {
298+
bool silent = type != PromiseHookType::kInit;
299+
wrap = new PromiseWrap(env, promise, silent);
298300
wrap->MakeWeak(wrap);
299301
} else if (type == PromiseHookType::kResolve) {
300302
// TODO(matthewloring): need to expose this through the async hooks api.
301303
}
302-
PromiseWrap* wrap = Unwrap<PromiseWrap>(promise);
303304
CHECK_NE(wrap, nullptr);
304305
if (type == PromiseHookType::kBefore) {
305306
PreCallbackExecution(wrap, false);
@@ -491,7 +492,8 @@ void LoadAsyncWrapperInfo(Environment* env) {
491492

492493
AsyncWrap::AsyncWrap(Environment* env,
493494
Local<Object> object,
494-
ProviderType provider)
495+
ProviderType provider,
496+
bool silent)
495497
: BaseObject(env, object),
496498
provider_type_(provider) {
497499
CHECK_NE(provider, PROVIDER_NONE);
@@ -501,7 +503,7 @@ AsyncWrap::AsyncWrap(Environment* env,
501503
persistent().SetWrapperClassId(NODE_ASYNC_ID_OFFSET + provider);
502504

503505
// Use AsyncReset() call to execute the init() callbacks.
504-
AsyncReset();
506+
AsyncReset(silent);
505507
}
506508

507509

@@ -513,10 +515,12 @@ AsyncWrap::~AsyncWrap() {
513515
// Generalized call for both the constructor and for handles that are pooled
514516
// and reused over their lifetime. This way a new uid can be assigned when
515517
// the resource is pulled out of the pool and put back into use.
516-
void AsyncWrap::AsyncReset() {
518+
void AsyncWrap::AsyncReset(bool silent) {
517519
async_id_ = env()->new_async_id();
518520
trigger_id_ = env()->get_init_trigger_id();
519521

522+
if (silent) return;
523+
520524
EmitAsyncInit(env(), object(),
521525
env()->async_hooks()->provider_string(provider_type()),
522526
async_id_, trigger_id_);

src/async-wrap.h

+3-2
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ class AsyncWrap : public BaseObject {
8686

8787
AsyncWrap(Environment* env,
8888
v8::Local<v8::Object> object,
89-
ProviderType provider);
89+
ProviderType provider,
90+
bool silent = false);
9091

9192
virtual ~AsyncWrap();
9293

@@ -116,7 +117,7 @@ class AsyncWrap : public BaseObject {
116117

117118
inline double get_trigger_id() const;
118119

119-
void AsyncReset();
120+
void AsyncReset(bool silent = false);
120121

121122
// Only call these within a valid HandleScope.
122123
// TODO(trevnorris): These should return a MaybeLocal.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
'use strict';
2+
3+
// Regression test for https://github.com/nodejs/node/issues/13237
4+
5+
const common = require('../common');
6+
const assert = require('assert');
7+
8+
const async_hooks = require('async_hooks');
9+
10+
const seenEvents = [];
11+
12+
const p = new Promise((resolve) => resolve(1));
13+
p.then(() => seenEvents.push('then'));
14+
15+
const hooks = async_hooks.createHook({
16+
init: common.mustNotCall(),
17+
18+
before: common.mustCall((id) => {
19+
assert.ok(id > 1);
20+
seenEvents.push('before');
21+
}),
22+
23+
after: common.mustCall((id) => {
24+
assert.ok(id > 1);
25+
seenEvents.push('after');
26+
hooks.disable();
27+
})
28+
});
29+
30+
setImmediate(() => {
31+
assert.deepStrictEqual(seenEvents, ['before', 'then', 'after']);
32+
});
33+
34+
hooks.enable(); // After `setImmediate` in order to not catch its init event.

0 commit comments

Comments
 (0)