From a6023ee0b514cf1c3f3b3bab8bb11886d79d2111 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Fri, 26 May 2017 17:00:43 +0200 Subject: [PATCH] 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: https://github.com/nodejs/node/issues/13237 PR-URL: https://github.com/nodejs/node/pull/13242 Reviewed-By: Andreas Madsen Reviewed-By: Matthew Loring --- src/async-wrap.cc | 20 ++++++----- src/async-wrap.h | 5 +-- .../test-async-wrap-promise-after-enabled.js | 34 +++++++++++++++++++ 3 files changed, 49 insertions(+), 10 deletions(-) create mode 100644 test/parallel/test-async-wrap-promise-after-enabled.js diff --git a/src/async-wrap.cc b/src/async-wrap.cc index 696d275a6e478e..0ea6c64a8be582 100644 --- a/src/async-wrap.cc +++ b/src/async-wrap.cc @@ -283,8 +283,8 @@ bool AsyncWrap::EmitAfter(Environment* env, double async_id) { class PromiseWrap : public AsyncWrap { public: - PromiseWrap(Environment* env, Local object) - : AsyncWrap(env, object, PROVIDER_PROMISE) {} + PromiseWrap(Environment* env, Local object, bool silent) + : AsyncWrap(env, object, PROVIDER_PROMISE, silent) {} size_t self_size() const override { return sizeof(*this); } }; @@ -293,13 +293,14 @@ static void PromiseHook(PromiseHookType type, Local promise, Local parent, void* arg) { Local context = promise->CreationContext(); Environment* env = Environment::GetCurrent(context); - if (type == PromiseHookType::kInit) { - PromiseWrap* wrap = new PromiseWrap(env, promise); + PromiseWrap* wrap = Unwrap(promise); + if (type == PromiseHookType::kInit || wrap == nullptr) { + bool silent = type != PromiseHookType::kInit; + wrap = new PromiseWrap(env, promise, silent); wrap->MakeWeak(wrap); } else if (type == PromiseHookType::kResolve) { // TODO(matthewloring): need to expose this through the async hooks api. } - PromiseWrap* wrap = Unwrap(promise); CHECK_NE(wrap, nullptr); if (type == PromiseHookType::kBefore) { PreCallbackExecution(wrap, false); @@ -491,7 +492,8 @@ void LoadAsyncWrapperInfo(Environment* env) { AsyncWrap::AsyncWrap(Environment* env, Local object, - ProviderType provider) + ProviderType provider, + bool silent) : BaseObject(env, object), provider_type_(provider) { CHECK_NE(provider, PROVIDER_NONE); @@ -501,7 +503,7 @@ AsyncWrap::AsyncWrap(Environment* env, persistent().SetWrapperClassId(NODE_ASYNC_ID_OFFSET + provider); // Use AsyncReset() call to execute the init() callbacks. - AsyncReset(); + AsyncReset(silent); } @@ -513,10 +515,12 @@ AsyncWrap::~AsyncWrap() { // Generalized call for both the constructor and for handles that are pooled // and reused over their lifetime. This way a new uid can be assigned when // the resource is pulled out of the pool and put back into use. -void AsyncWrap::AsyncReset() { +void AsyncWrap::AsyncReset(bool silent) { async_id_ = env()->new_async_id(); trigger_id_ = env()->get_init_trigger_id(); + if (silent) return; + EmitAsyncInit(env(), object(), env()->async_hooks()->provider_string(provider_type()), async_id_, trigger_id_); diff --git a/src/async-wrap.h b/src/async-wrap.h index d3676a01c06705..fa37ea13a65993 100644 --- a/src/async-wrap.h +++ b/src/async-wrap.h @@ -86,7 +86,8 @@ class AsyncWrap : public BaseObject { AsyncWrap(Environment* env, v8::Local object, - ProviderType provider); + ProviderType provider, + bool silent = false); virtual ~AsyncWrap(); @@ -116,7 +117,7 @@ class AsyncWrap : public BaseObject { inline double get_trigger_id() const; - void AsyncReset(); + void AsyncReset(bool silent = false); // Only call these within a valid HandleScope. // TODO(trevnorris): These should return a MaybeLocal. diff --git a/test/parallel/test-async-wrap-promise-after-enabled.js b/test/parallel/test-async-wrap-promise-after-enabled.js new file mode 100644 index 00000000000000..475411a3dae531 --- /dev/null +++ b/test/parallel/test-async-wrap-promise-after-enabled.js @@ -0,0 +1,34 @@ +'use strict'; + +// Regression test for https://github.com/nodejs/node/issues/13237 + +const common = require('../common'); +const assert = require('assert'); + +const async_hooks = require('async_hooks'); + +const seenEvents = []; + +const p = new Promise((resolve) => resolve(1)); +p.then(() => seenEvents.push('then')); + +const hooks = async_hooks.createHook({ + init: common.mustNotCall(), + + before: common.mustCall((id) => { + assert.ok(id > 1); + seenEvents.push('before'); + }), + + after: common.mustCall((id) => { + assert.ok(id > 1); + seenEvents.push('after'); + hooks.disable(); + }) +}); + +setImmediate(() => { + assert.deepStrictEqual(seenEvents, ['before', 'then', 'after']); +}); + +hooks.enable(); // After `setImmediate` in order to not catch its init event.