diff --git a/Makefile b/Makefile index 825baa1ecb62a7..cd0bfd312224d2 100644 --- a/Makefile +++ b/Makefile @@ -202,6 +202,7 @@ test: all $(MAKE) build-addons-napi $(MAKE) cctest $(PYTHON) tools/test.py --mode=release -J \ + $(CI_ASYNC_HOOKS) \ $(CI_JS_SUITES) \ $(CI_NATIVE_SUITES) $(MAKE) lint @@ -334,7 +335,8 @@ test-all-valgrind: test-build $(PYTHON) tools/test.py --mode=debug,release --valgrind CI_NATIVE_SUITES := addons addons-napi -CI_JS_SUITES := abort async-hooks doctool inspector known_issues message parallel pseudo-tty sequential +CI_ASYNC_HOOKS := async-hooks +CI_JS_SUITES := abort doctool inspector known_issues message parallel pseudo-tty sequential # Build and test addons without building anything else test-ci-native: LOGLEVEL := info @@ -347,7 +349,7 @@ test-ci-native: | test/addons/.buildstamp test/addons-napi/.buildstamp test-ci-js: | clear-stalled $(PYTHON) tools/test.py $(PARALLEL_ARGS) -p tap --logfile test.tap \ --mode=release --flaky-tests=$(FLAKY_TESTS) \ - $(TEST_CI_ARGS) $(CI_JS_SUITES) + $(TEST_CI_ARGS) $(CI_ASYNC_HOOKS) $(CI_JS_SUITES) # Clean up any leftover processes, error if found. ps awwx | grep Release/node | grep -v grep | cat @PS_OUT=`ps awwx | grep Release/node | grep -v grep | awk '{print $$1}'`; \ @@ -360,7 +362,7 @@ test-ci: | clear-stalled build-addons build-addons-napi out/Release/cctest --gtest_output=tap:cctest.tap $(PYTHON) tools/test.py $(PARALLEL_ARGS) -p tap --logfile test.tap \ --mode=release --flaky-tests=$(FLAKY_TESTS) \ - $(TEST_CI_ARGS) $(CI_JS_SUITES) $(CI_NATIVE_SUITES) + $(TEST_CI_ARGS) $(CI_ASYNC_HOOKS) $(CI_JS_SUITES) $(CI_NATIVE_SUITES) # Clean up any leftover processes, error if found. ps awwx | grep Release/node | grep -v grep | cat @PS_OUT=`ps awwx | grep Release/node | grep -v grep | awk '{print $$1}'`; \ @@ -434,6 +436,14 @@ test-timers-clean: test-async-hooks: $(PYTHON) tools/test.py --mode=release async-hooks +test-with-async-hooks: + $(MAKE) build-addons + $(MAKE) build-addons-napi + $(MAKE) cctest + NODE_TEST_WITH_ASYNC_HOOKS=1 $(PYTHON) tools/test.py --mode=release -J \ + $(CI_JS_SUITES) \ + $(CI_NATIVE_SUITES) + ifneq ("","$(wildcard deps/v8/tools/run-tests.py)") test-v8: v8 diff --git a/src/async-wrap.cc b/src/async-wrap.cc index 7ccc108d375925..a16d93888925c6 100644 --- a/src/async-wrap.cc +++ b/src/async-wrap.cc @@ -243,7 +243,7 @@ void AsyncWrap::EmitAfter(Environment* env, double async_id) { class PromiseWrap : public AsyncWrap { public: PromiseWrap(Environment* env, Local object, bool silent) - : AsyncWrap(env, object, PROVIDER_PROMISE, silent) { + : AsyncWrap(env, object, silent) { MakeWeak(this); } size_t self_size() const override { return sizeof(*this); } @@ -451,7 +451,8 @@ void AsyncWrap::ClearAsyncIdStack(const FunctionCallbackInfo& args) { void AsyncWrap::AsyncReset(const FunctionCallbackInfo& args) { AsyncWrap* wrap; ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); - wrap->AsyncReset(); + double execution_async_id = args[0]->IsNumber() ? args[0]->NumberValue() : -1; + wrap->AsyncReset(execution_async_id); } @@ -574,7 +575,7 @@ void LoadAsyncWrapperInfo(Environment* env) { AsyncWrap::AsyncWrap(Environment* env, Local object, ProviderType provider, - bool silent) + double execution_async_id) : BaseObject(env, object), provider_type_(provider) { CHECK_NE(provider, PROVIDER_NONE); @@ -584,7 +585,23 @@ AsyncWrap::AsyncWrap(Environment* env, persistent().SetWrapperClassId(NODE_ASYNC_ID_OFFSET + provider); // Use AsyncReset() call to execute the init() callbacks. - AsyncReset(silent); + AsyncReset(execution_async_id); +} + + +// This is specifically used by the PromiseWrap constructor. +AsyncWrap::AsyncWrap(Environment* env, + Local object, + bool silent) + : BaseObject(env, object), + provider_type_(PROVIDER_PROMISE) { + CHECK_GE(object->InternalFieldCount(), 1); + + // Shift provider value over to prevent id collision. + persistent().SetWrapperClassId(NODE_ASYNC_ID_OFFSET + provider_type_); + + // Use AsyncReset() call to execute the init() callbacks. + AsyncReset(-1, silent); } @@ -596,8 +613,9 @@ 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(bool silent) { - async_id_ = env()->new_async_id(); +void AsyncWrap::AsyncReset(double execution_async_id, bool silent) { + async_id_ = + execution_async_id == -1 ? env()->new_async_id() : execution_async_id; trigger_async_id_ = env()->get_init_trigger_async_id(); if (silent) return; diff --git a/src/async-wrap.h b/src/async-wrap.h index e860afb90502d7..d24eb0d46d51b6 100644 --- a/src/async-wrap.h +++ b/src/async-wrap.h @@ -95,7 +95,7 @@ class AsyncWrap : public BaseObject { AsyncWrap(Environment* env, v8::Local object, ProviderType provider, - bool silent = false); + double execution_async_id = -1); virtual ~AsyncWrap(); @@ -133,7 +133,7 @@ class AsyncWrap : public BaseObject { inline double get_trigger_async_id() const; - void AsyncReset(bool silent = false); + void AsyncReset(double execution_async_id = -1, bool silent = false); // Only call these within a valid HandleScope. v8::MaybeLocal MakeCallback(const v8::Local cb, @@ -150,6 +150,10 @@ class AsyncWrap : public BaseObject { virtual size_t self_size() const = 0; private: + friend class PromiseWrap; + + // This is specifically used by the PromiseWrap constructor. + AsyncWrap(Environment* env, v8::Local promise, bool silent); inline AsyncWrap(); const ProviderType provider_type_; // Because the values may be Reset(), cannot be made const. diff --git a/test/addons/async-hooks-promise/test.js b/test/addons/async-hooks-promise/test.js index b0af8806bd665f..e7923c9c57a6e9 100644 --- a/test/addons/async-hooks-promise/test.js +++ b/test/addons/async-hooks-promise/test.js @@ -5,6 +5,11 @@ const assert = require('assert'); const async_hooks = require('async_hooks'); const binding = require(`./build/${common.buildType}/binding`); +if (process.env.NODE_TEST_WITH_ASYNC_HOOKS) { + common.skip('cannot test with env var NODE_TEST_WITH_ASYNC_HOOKS'); + return; +} + // Baseline to make sure the internal field isn't being set. assert.strictEqual( binding.getPromiseField(Promise.resolve(1)), diff --git a/test/common/index.js b/test/common/index.js index c4f899c9ec5d15..278f9f6a781474 100644 --- a/test/common/index.js +++ b/test/common/index.js @@ -79,18 +79,21 @@ if (process.env.NODE_TEST_WITH_ASYNC_HOOKS) { if (destroyListList[id] !== undefined) { process._rawDebug(destroyListList[id]); process._rawDebug(); - throw new Error(`same id added twice (${id})`); + throw new Error(`same id added to destroy list twice (${id})`); } destroyListList[id] = new Error().stack; _queueDestroyAsyncId(id); }; require('async_hooks').createHook({ - init(id, ty, tr, h) { + init(id, ty, tr, r) { if (initHandles[id]) { + process._rawDebug( + `Is same resource: ${r === initHandles[id].resource}`); + process._rawDebug(`Previous stack:\n${initHandles[id].stack}\n`); throw new Error(`init called twice for same id (${id})`); } - initHandles[id] = h; + initHandles[id] = { resource: r, stack: new Error().stack.substr(6) }; }, before() { }, after() { },