From eacd2e9c4cd27d60bd3d29f286e833070e6d7003 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Tue, 8 Aug 2017 19:56:02 +0200 Subject: [PATCH 1/8] src: refactor `#include` handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `node_internals.h` already includes the most common headers, so double includes can be avoided in a lot of cases. Also don’t include `node_internals.h` from `node.h` implicitly anymore, as that is mostly unnecessary. PR-URL: https://github.com/nodejs/node/pull/14697 Reviewed-By: Tobias Nießen Reviewed-By: Refael Ackermann Reviewed-By: James M Snell --- src/async-wrap-inl.h | 5 ----- src/backtrace_posix.cc | 2 +- src/env-inl.h | 1 + src/env.cc | 4 +--- src/env.h | 4 ++++ src/inspector_agent.cc | 5 +---- src/node.cc | 1 - src/node.h | 4 ---- src/node_api.cc | 2 -- src/node_buffer.h | 3 +-- src/node_constants.cc | 4 +--- src/node_contextify.cc | 5 ----- src/node_counters.h | 2 +- src/node_dtrace.cc | 5 +---- src/node_dtrace.h | 4 +--- src/node_file.cc | 4 ---- src/node_i18n.h | 2 +- src/node_internals.h | 2 ++ src/node_javascript.h | 3 +-- src/node_main.cc | 2 ++ src/node_os.cc | 5 +---- src/node_perf.cc | 6 +----- src/node_url.cc | 6 ------ src/node_util.cc | 5 +---- src/node_watchdog.cc | 2 -- src/string_search.h | 2 +- src/tls_wrap.cc | 2 -- src/util.cc | 1 - test/cctest/node_test_fixture.h | 1 + test/cctest/test_environment.cc | 4 +--- 30 files changed, 25 insertions(+), 73 deletions(-) diff --git a/src/async-wrap-inl.h b/src/async-wrap-inl.h index 4f8b7c3f8dd914..ed5a0c0d279048 100644 --- a/src/async-wrap-inl.h +++ b/src/async-wrap-inl.h @@ -27,12 +27,7 @@ #include "async-wrap.h" #include "base-object.h" #include "base-object-inl.h" -#include "env.h" -#include "env-inl.h" #include "node_internals.h" -#include "util.h" -#include "util-inl.h" -#include "v8.h" namespace node { diff --git a/src/backtrace_posix.cc b/src/backtrace_posix.cc index 8fd798757a544a..0c69d820e7212a 100644 --- a/src/backtrace_posix.cc +++ b/src/backtrace_posix.cc @@ -1,4 +1,4 @@ -#include "node.h" +#include "node_internals.h" #if defined(__linux__) #include diff --git a/src/env-inl.h b/src/env-inl.h index 404c06c6f453bd..ddfb5fd93ffcf3 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -31,6 +31,7 @@ #include "util-inl.h" #include "uv.h" #include "v8.h" +#include "node_perf_common.h" #include #include diff --git a/src/env.cc b/src/env.cc index 076198cd626a81..ba5caa1ec3acca 100644 --- a/src/env.cc +++ b/src/env.cc @@ -1,7 +1,5 @@ -#include "env.h" -#include "env-inl.h" +#include "node_internals.h" #include "async-wrap.h" -#include "v8.h" #include "v8-profiler.h" #if defined(_MSC_VER) diff --git a/src/env.h b/src/env.h index 13b1f4cb137bce..4af87192df8128 100644 --- a/src/env.h +++ b/src/env.h @@ -48,6 +48,10 @@ struct nghttp2_rcbuf; namespace node { +namespace performance { +struct performance_state; +} + // Pick an index that's hopefully out of the way when we're embedded inside // another application. Performance-wise or memory-wise it doesn't matter: // Context::SetAlignedPointerInEmbedderData() is backed by a FixedArray, diff --git a/src/inspector_agent.cc b/src/inspector_agent.cc index 828006ecf2fbb4..9f33b3d31e69e6 100644 --- a/src/inspector_agent.cc +++ b/src/inspector_agent.cc @@ -1,12 +1,9 @@ #include "inspector_agent.h" #include "inspector_io.h" -#include "env.h" -#include "env-inl.h" -#include "node.h" +#include "node_internals.h" #include "v8-inspector.h" #include "v8-platform.h" -#include "util.h" #include "zlib.h" #include "libplatform/libplatform.h" diff --git a/src/node.cc b/src/node.cc index 664ae22a9ae258..30cbb11efa9e25 100644 --- a/src/node.cc +++ b/src/node.cc @@ -19,7 +19,6 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. -#include "node.h" #include "node_buffer.h" #include "node_constants.h" #include "node_javascript.h" diff --git a/src/node.h b/src/node.h index fbd17d2ca09c7f..5fe7bde4289ae4 100644 --- a/src/node.h +++ b/src/node.h @@ -169,10 +169,6 @@ NODE_EXTERN v8::Local MakeCallback( } // namespace node -#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#include "node_internals.h" -#endif - #include #include diff --git a/src/node_api.cc b/src/node_api.cc index 7bb97c8076fac3..8c55e64b7cda05 100644 --- a/src/node_api.cc +++ b/src/node_api.cc @@ -14,10 +14,8 @@ #include #include #include -#include "uv.h" #include "node_api.h" #include "node_internals.h" -#include "util.h" #define NAPI_VERSION 1 diff --git a/src/node_buffer.h b/src/node_buffer.h index acf9b23c3b3256..d680eb90eb1649 100644 --- a/src/node_buffer.h +++ b/src/node_buffer.h @@ -22,8 +22,7 @@ #ifndef SRC_NODE_BUFFER_H_ #define SRC_NODE_BUFFER_H_ -#include "node.h" -#include "v8.h" +#include "node_internals.h" namespace node { diff --git a/src/node_constants.cc b/src/node_constants.cc index b8b97b21f9ea39..7fd303dd32fed0 100644 --- a/src/node_constants.cc +++ b/src/node_constants.cc @@ -20,10 +20,8 @@ // USE OR OTHER DEALINGS IN THE SOFTWARE. #include "node_constants.h" -#include "env.h" -#include "env-inl.h" +#include "node_internals.h" -#include "uv.h" #include "zlib.h" #include diff --git a/src/node_contextify.cc b/src/node_contextify.cc index b04bd6253e9386..c73db420f18b4e 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -19,15 +19,10 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. -#include "node.h" #include "node_internals.h" #include "node_watchdog.h" #include "base-object.h" #include "base-object-inl.h" -#include "env.h" -#include "env-inl.h" -#include "util.h" -#include "util-inl.h" #include "v8-debug.h" namespace node { diff --git a/src/node_counters.h b/src/node_counters.h index 5d866aedb57677..c8a1a88f0b25e0 100644 --- a/src/node_counters.h +++ b/src/node_counters.h @@ -24,7 +24,7 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#include "node.h" +#include "node_internals.h" #ifdef HAVE_PERFCTR #include "node_win32_perfctr_provider.h" diff --git a/src/node_dtrace.cc b/src/node_dtrace.cc index 94d06a7404ed33..4ff2f22d8e835a 100644 --- a/src/node_dtrace.cc +++ b/src/node_dtrace.cc @@ -43,10 +43,7 @@ #define NODE_GC_DONE(arg0, arg1, arg2) #endif -#include "env.h" -#include "env-inl.h" - -#include "util.h" +#include "node_internals.h" #include diff --git a/src/node_dtrace.h b/src/node_dtrace.h index c22bf4e7fc0f12..d16e23ddb384b7 100644 --- a/src/node_dtrace.h +++ b/src/node_dtrace.h @@ -24,9 +24,7 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#include "node.h" -#include "v8.h" -#include "env.h" +#include "node_internals.h" extern "C" { /* diff --git a/src/node_file.cc b/src/node_file.cc index cf7f1df5eb98bc..b9b3d34f346e56 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -19,17 +19,13 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. -#include "node.h" #include "node_buffer.h" #include "node_internals.h" #include "node_stat_watcher.h" -#include "env.h" -#include "env-inl.h" #include "req-wrap.h" #include "req-wrap-inl.h" #include "string_bytes.h" -#include "util.h" #include #include diff --git a/src/node_i18n.h b/src/node_i18n.h index adf9feb414df5c..70a0c79f76cf30 100644 --- a/src/node_i18n.h +++ b/src/node_i18n.h @@ -24,7 +24,7 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#include "node.h" +#include "node_internals.h" #include #if defined(NODE_HAVE_I18N_SUPPORT) diff --git a/src/node_internals.h b/src/node_internals.h index 6faf2750d4d039..9b6fae9d6a9cdd 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -27,6 +27,8 @@ #include "node.h" #include "util.h" #include "util-inl.h" +#include "env.h" +#include "env-inl.h" #include "uv.h" #include "v8.h" #include "tracing/trace_event.h" diff --git a/src/node_javascript.h b/src/node_javascript.h index 3e8528fd211fb6..664778091ff669 100644 --- a/src/node_javascript.h +++ b/src/node_javascript.h @@ -24,8 +24,7 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#include "v8.h" -#include "env.h" +#include "node_internals.h" namespace node { diff --git a/src/node_main.cc b/src/node_main.cc index 7d6d9b1a01bbd4..2a511b92996e86 100644 --- a/src/node_main.cc +++ b/src/node_main.cc @@ -20,8 +20,10 @@ // USE OR OTHER DEALINGS IN THE SOFTWARE. #include "node.h" +#include #ifdef _WIN32 +#include #include #include diff --git a/src/node_os.cc b/src/node_os.cc index c71ca401ed6609..f09cd6fa5a03ff 100644 --- a/src/node_os.cc +++ b/src/node_os.cc @@ -19,10 +19,7 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. -#include "node.h" -#include "v8.h" -#include "env.h" -#include "env-inl.h" +#include "node_internals.h" #include "string_bytes.h" #include diff --git a/src/node_perf.cc b/src/node_perf.cc index 48917d5d4ea971..098cf35220a400 100644 --- a/src/node_perf.cc +++ b/src/node_perf.cc @@ -1,9 +1,5 @@ -#include "node.h" -#include "v8.h" -#include "env.h" -#include "env-inl.h" +#include "node_internals.h" #include "node_perf.h" -#include "uv.h" #include diff --git a/src/node_url.cc b/src/node_url.cc index f8adc7d7af5509..20a869ad7afc63 100644 --- a/src/node_url.cc +++ b/src/node_url.cc @@ -1,11 +1,5 @@ #include "node_url.h" -#include "node.h" #include "node_internals.h" -#include "env.h" -#include "env-inl.h" -#include "util.h" -#include "util-inl.h" -#include "v8.h" #include "base-object.h" #include "base-object-inl.h" #include "node_i18n.h" diff --git a/src/node_util.cc b/src/node_util.cc index bbbea9ea2254dd..ab1f3c9f91257d 100644 --- a/src/node_util.cc +++ b/src/node_util.cc @@ -1,8 +1,5 @@ -#include "node.h" +#include "node_internals.h" #include "node_watchdog.h" -#include "v8.h" -#include "env.h" -#include "env-inl.h" namespace node { namespace util { diff --git a/src/node_watchdog.cc b/src/node_watchdog.cc index f4020e56f7fb61..af4e9f6fcfe3c2 100644 --- a/src/node_watchdog.cc +++ b/src/node_watchdog.cc @@ -21,8 +21,6 @@ #include "node_watchdog.h" #include "node_internals.h" -#include "util.h" -#include "util-inl.h" #include namespace node { diff --git a/src/string_search.h b/src/string_search.h index dfdb8e9a160460..73e90f5873f767 100644 --- a/src/string_search.h +++ b/src/string_search.h @@ -7,7 +7,7 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#include "node.h" +#include "node_internals.h" #include namespace node { diff --git a/src/tls_wrap.cc b/src/tls_wrap.cc index b5829cf5b82f6c..dce05fabd62160 100644 --- a/src/tls_wrap.cc +++ b/src/tls_wrap.cc @@ -31,8 +31,6 @@ #include "node_internals.h" #include "stream_base.h" #include "stream_base-inl.h" -#include "util.h" -#include "util-inl.h" namespace node { diff --git a/src/util.cc b/src/util.cc index 4a89b3a42f24b0..ef93d16968e465 100644 --- a/src/util.cc +++ b/src/util.cc @@ -19,7 +19,6 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. -#include "util.h" #include "string_bytes.h" #include "node_buffer.h" #include "node_internals.h" diff --git a/test/cctest/node_test_fixture.h b/test/cctest/node_test_fixture.h index 79027d25ad8c8d..263f7b96f9daec 100644 --- a/test/cctest/node_test_fixture.h +++ b/test/cctest/node_test_fixture.h @@ -4,6 +4,7 @@ #include #include "gtest/gtest.h" #include "node.h" +#include "node_platform.h" #include "env.h" #include "v8.h" #include "libplatform/libplatform.h" diff --git a/test/cctest/test_environment.cc b/test/cctest/test_environment.cc index 4651e865a99e7d..8beacfa95ece7e 100644 --- a/test/cctest/test_environment.cc +++ b/test/cctest/test_environment.cc @@ -1,6 +1,4 @@ -#include "node.h" -#include "env.h" -#include "v8.h" +#include "node_internals.h" #include "libplatform/libplatform.h" #include From 95976d807c793a187055a4c3b1b37a1c7a1f5d55 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Tue, 8 Aug 2017 20:01:18 +0200 Subject: [PATCH 2/8] src: make in_makecallback() getter const MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/14697 Reviewed-By: Tobias Nießen Reviewed-By: Refael Ackermann Reviewed-By: James M Snell --- src/env-inl.h | 2 +- src/env.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/env-inl.h b/src/env-inl.h index ddfb5fd93ffcf3..93518fe5aefaa2 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -223,7 +223,7 @@ inline Environment::AsyncCallbackScope::~AsyncCallbackScope() { env_->makecallback_cntr_--; } -inline bool Environment::AsyncCallbackScope::in_makecallback() { +inline bool Environment::AsyncCallbackScope::in_makecallback() const { return env_->makecallback_cntr_ > 1; } diff --git a/src/env.h b/src/env.h index 4af87192df8128..dcaeefd226096c 100644 --- a/src/env.h +++ b/src/env.h @@ -463,7 +463,7 @@ class Environment { AsyncCallbackScope() = delete; explicit AsyncCallbackScope(Environment* env); ~AsyncCallbackScope(); - inline bool in_makecallback(); + inline bool in_makecallback() const; private: Environment* env_; From f3ef344ac927a9ad67f35c8d1f0ec6f61ec670b4 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Tue, 8 Aug 2017 20:02:55 +0200 Subject: [PATCH 3/8] src: refactor async callback handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Merge the two almost-but-not-quite identical `MakeCallback()` implementations - Provide a public `CallbackScope` class for embedders in order to enable `MakeCallback()`-like behaviour without tying that to calling a JS function PR-URL: https://github.com/nodejs/node/pull/14697 Reviewed-By: Tobias Nießen Reviewed-By: Refael Ackermann Reviewed-By: James M Snell --- src/async-wrap.cc | 69 +------ src/node.cc | 169 ++++++++++++------ src/node.h | 40 +++++ src/node_internals.h | 8 + test/addons/callback-scope/binding.cc | 39 ++++ test/addons/callback-scope/binding.gyp | 9 + .../addons/callback-scope/test-async-hooks.js | 22 +++ test/addons/callback-scope/test.js | 17 ++ 8 files changed, 256 insertions(+), 117 deletions(-) create mode 100644 test/addons/callback-scope/binding.cc create mode 100644 test/addons/callback-scope/binding.gyp create mode 100644 test/addons/callback-scope/test-async-hooks.js create mode 100644 test/addons/callback-scope/test.js diff --git a/src/async-wrap.cc b/src/async-wrap.cc index 391a684759cd03..fe53d279947a96 100644 --- a/src/async-wrap.cc +++ b/src/async-wrap.cc @@ -657,73 +657,8 @@ void AsyncWrap::EmitAsyncInit(Environment* env, MaybeLocal AsyncWrap::MakeCallback(const Local cb, int argc, Local* argv) { - CHECK(env()->context() == env()->isolate()->GetCurrentContext()); - - Environment::AsyncCallbackScope callback_scope(env()); - - Environment::AsyncHooks::ExecScope exec_scope(env(), - get_id(), - get_trigger_id()); - - // Return v8::Undefined() because returning an empty handle will cause - // ToLocalChecked() to abort. - if (env()->using_domains() && DomainEnter(env(), object())) { - return Undefined(env()->isolate()); - } - - // No need to check a return value because the application will exit if an - // exception occurs. - AsyncWrap::EmitBefore(env(), get_id()); - - MaybeLocal ret = cb->Call(env()->context(), object(), argc, argv); - - if (ret.IsEmpty()) { - return ret; - } - - AsyncWrap::EmitAfter(env(), get_id()); - - // Return v8::Undefined() because returning an empty handle will cause - // ToLocalChecked() to abort. - if (env()->using_domains() && DomainExit(env(), object())) { - return Undefined(env()->isolate()); - } - - exec_scope.Dispose(); - - if (callback_scope.in_makecallback()) { - return ret; - } - - Environment::TickInfo* tick_info = env()->tick_info(); - - if (tick_info->length() == 0) { - env()->isolate()->RunMicrotasks(); - } - - // Make sure the stack unwound properly. If there are nested MakeCallback's - // then it should return early and not reach this code. - CHECK_EQ(env()->current_async_id(), 0); - CHECK_EQ(env()->trigger_id(), 0); - - Local process = env()->process_object(); - - if (tick_info->length() == 0) { - tick_info->set_index(0); - return ret; - } - - MaybeLocal rcheck = - env()->tick_callback_function()->Call(env()->context(), - process, - 0, - nullptr); - - // Make sure the stack unwound properly. - CHECK_EQ(env()->current_async_id(), 0); - CHECK_EQ(env()->trigger_id(), 0); - - return rcheck.IsEmpty() ? MaybeLocal() : ret; + async_context context { get_id(), get_trigger_id() }; + return InternalMakeCallback(env(), object(), cb, argc, argv, context); } diff --git a/src/node.cc b/src/node.cc index 30cbb11efa9e25..cdbb7edb9eb0d7 100644 --- a/src/node.cc +++ b/src/node.cc @@ -161,6 +161,7 @@ using v8::SealHandleScope; using v8::String; using v8::TryCatch; using v8::Uint32Array; +using v8::Undefined; using v8::V8; using v8::Value; @@ -1311,85 +1312,153 @@ void AddPromiseHook(v8::Isolate* isolate, promise_hook_func fn, void* arg) { env->AddPromiseHook(fn, arg); } +class InternalCallbackScope { + public: + InternalCallbackScope(Environment* env, + Local object, + const async_context& asyncContext); + ~InternalCallbackScope(); + void Close(); + + inline bool Failed() const { return failed_; } + inline void MarkAsFailed() { failed_ = true; } + inline bool IsInnerMakeCallback() const { + return callback_scope_.in_makecallback(); + } + + private: + Environment* env_; + async_context async_context_; + v8::Local object_; + Environment::AsyncCallbackScope callback_scope_; + bool failed_ = false; + bool pushed_ids_ = false; + bool closed_ = false; +}; -MaybeLocal MakeCallback(Environment* env, - Local recv, - const Local callback, - int argc, - Local argv[], - async_context asyncContext) { - // If you hit this assertion, you forgot to enter the v8::Context first. - CHECK_EQ(env->context(), env->isolate()->GetCurrentContext()); +CallbackScope::CallbackScope(Isolate* isolate, + Local object, + async_context asyncContext) + : private_(new InternalCallbackScope(Environment::GetCurrent(isolate), + object, + asyncContext)), + try_catch_(isolate) { + try_catch_.SetVerbose(true); +} - Local object; +CallbackScope::~CallbackScope() { + if (try_catch_.HasCaught()) + private_->MarkAsFailed(); + delete private_; +} - Environment::AsyncCallbackScope callback_scope(env); - bool disposed_domain = false; +InternalCallbackScope::InternalCallbackScope(Environment* env, + Local object, + const async_context& asyncContext) + : env_(env), + async_context_(asyncContext), + object_(object), + callback_scope_(env) { + CHECK(!object.IsEmpty()); - if (recv->IsObject()) { - object = recv.As(); - } + // If you hit this assertion, you forgot to enter the v8::Context first. + CHECK_EQ(env->context(), env->isolate()->GetCurrentContext()); if (env->using_domains()) { - CHECK(recv->IsObject()); - disposed_domain = DomainEnter(env, object); - if (disposed_domain) return Undefined(env->isolate()); + failed_ = DomainEnter(env, object_); + if (failed_) + return; } - MaybeLocal ret; + if (asyncContext.async_id != 0) { + // No need to check a return value because the application will exit if + // an exception occurs. + AsyncWrap::EmitBefore(env, asyncContext.async_id); + } - { - AsyncHooks::ExecScope exec_scope(env, asyncContext.async_id, - asyncContext.trigger_async_id); + env->async_hooks()->push_ids(async_context_.async_id, + async_context_.trigger_async_id); + pushed_ids_ = true; +} - if (asyncContext.async_id != 0) { - // No need to check a return value because the application will exit if - // an exception occurs. - AsyncWrap::EmitBefore(env, asyncContext.async_id); - } +InternalCallbackScope::~InternalCallbackScope() { + Close(); +} - ret = callback->Call(env->context(), recv, argc, argv); +void InternalCallbackScope::Close() { + if (closed_) return; + closed_ = true; - if (ret.IsEmpty()) { - // NOTE: For backwards compatibility with public API we return Undefined() - // if the top level call threw. - return callback_scope.in_makecallback() ? - ret : Undefined(env->isolate()); - } + if (pushed_ids_) + env_->async_hooks()->pop_ids(async_context_.async_id); - if (asyncContext.async_id != 0) { - AsyncWrap::EmitAfter(env, asyncContext.async_id); - } + if (failed_) return; + + if (async_context_.async_id != 0) { + AsyncWrap::EmitAfter(env_, async_context_.async_id); } - if (env->using_domains()) { - disposed_domain = DomainExit(env, object); - if (disposed_domain) return Undefined(env->isolate()); + if (env_->using_domains()) { + failed_ = DomainExit(env_, object_); + if (failed_) return; } - if (callback_scope.in_makecallback()) { - return ret; + if (IsInnerMakeCallback()) { + return; } - Environment::TickInfo* tick_info = env->tick_info(); + Environment::TickInfo* tick_info = env_->tick_info(); if (tick_info->length() == 0) { - env->isolate()->RunMicrotasks(); + env_->isolate()->RunMicrotasks(); } // Make sure the stack unwound properly. If there are nested MakeCallback's // then it should return early and not reach this code. - CHECK_EQ(env->current_async_id(), asyncContext.async_id); - CHECK_EQ(env->trigger_id(), asyncContext.trigger_async_id); + CHECK_EQ(env_->current_async_id(), 0); + CHECK_EQ(env_->trigger_id(), 0); - Local process = env->process_object(); + Local process = env_->process_object(); if (tick_info->length() == 0) { tick_info->set_index(0); - return ret; + return; + } + + CHECK_EQ(env_->current_async_id(), 0); + CHECK_EQ(env_->trigger_id(), 0); + + if (env_->tick_callback_function()->Call(process, 0, nullptr).IsEmpty()) { + failed_ = true; + } +} + +MaybeLocal InternalMakeCallback(Environment* env, + Local recv, + const Local callback, + int argc, + Local argv[], + async_context asyncContext) { + InternalCallbackScope scope(env, recv, asyncContext); + if (scope.Failed()) { + return Undefined(env->isolate()); + } + + MaybeLocal ret; + + { + ret = callback->Call(env->context(), recv, argc, argv); + + if (ret.IsEmpty()) { + // NOTE: For backwards compatibility with public API we return Undefined() + // if the top level call threw. + scope.MarkAsFailed(); + return scope.IsInnerMakeCallback() ? ret : Undefined(env->isolate()); + } } - if (env->tick_callback_function()->Call(process, 0, nullptr).IsEmpty()) { + scope.Close(); + if (scope.Failed()) { return Undefined(env->isolate()); } @@ -1442,8 +1511,8 @@ MaybeLocal MakeCallback(Isolate* isolate, // the two contexts need not be the same. Environment* env = Environment::GetCurrent(callback->CreationContext()); Context::Scope context_scope(env->context()); - return MakeCallback(env, recv.As(), callback, argc, argv, - asyncContext); + return InternalMakeCallback(env, recv, callback, + argc, argv, asyncContext); } diff --git a/src/node.h b/src/node.h index 5fe7bde4289ae4..6558a9ee6da96e 100644 --- a/src/node.h +++ b/src/node.h @@ -570,6 +570,36 @@ NODE_EXTERN async_context EmitAsyncInit(v8::Isolate* isolate, NODE_EXTERN void EmitAsyncDestroy(v8::Isolate* isolate, async_context asyncContext); +class InternalCallbackScope; + +/* This class works like `MakeCallback()` in that it sets up a specific + * asyncContext as the current one and informs the async_hooks and domains + * modules that this context is currently active. + * + * `MakeCallback()` is a wrapper around this class as well as + * `Function::Call()`. Either one of these mechanisms needs to be used for + * top-level calls into JavaScript (i.e. without any existing JS stack). + * + * This object should be stack-allocated to ensure that it is contained in a + * valid HandleScope. + */ +class NODE_EXTERN CallbackScope { + public: + CallbackScope(v8::Isolate* isolate, + v8::Local resource, + async_context asyncContext); + ~CallbackScope(); + + private: + InternalCallbackScope* private_; + v8::TryCatch try_catch_; + + void operator=(const CallbackScope&) = delete; + void operator=(CallbackScope&&) = delete; + CallbackScope(const CallbackScope&) = delete; + CallbackScope(CallbackScope&&) = delete; +}; + /* An API specific to emit before/after callbacks is unnecessary because * MakeCallback will automatically call them for you. * @@ -659,6 +689,16 @@ class AsyncResource { async_id get_trigger_async_id() const { return async_context_.trigger_async_id; } + + protected: + class CallbackScope : public node::CallbackScope { + public: + explicit CallbackScope(AsyncResource* res) + : node::CallbackScope(res->isolate_, + res->resource_.Get(res->isolate_), + res->async_context_) {} + }; + private: v8::Isolate* isolate_; v8::Persistent resource_; diff --git a/src/node_internals.h b/src/node_internals.h index 9b6fae9d6a9cdd..9371d442ad0ea5 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -286,6 +286,14 @@ static v8::MaybeLocal New(Environment* env, } } // namespace Buffer +v8::MaybeLocal InternalMakeCallback( + Environment* env, + v8::Local recv, + const v8::Local callback, + int argc, + v8::Local argv[], + async_context asyncContext); + } // namespace node #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS diff --git a/test/addons/callback-scope/binding.cc b/test/addons/callback-scope/binding.cc new file mode 100644 index 00000000000000..31317e5dae236f --- /dev/null +++ b/test/addons/callback-scope/binding.cc @@ -0,0 +1,39 @@ +#include "node.h" +#include "v8.h" + +#include +#include + +namespace { + +void RunInCallbackScope(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = args.GetIsolate(); + + assert(args.Length() == 4); + assert(args[0]->IsObject()); + assert(args[1]->IsNumber()); + assert(args[2]->IsNumber()); + assert(args[3]->IsFunction()); + + node::async_context asyncContext = { + args[1].As()->Value(), + args[2].As()->Value() + }; + + node::CallbackScope scope(isolate, args[0].As(), asyncContext); + v8::Local fn = args[3].As(); + + v8::MaybeLocal ret = + fn->Call(isolate->GetCurrentContext(), args[0], 0, nullptr); + + if (!ret.IsEmpty()) + args.GetReturnValue().Set(ret.ToLocalChecked()); +} + +void Initialize(v8::Local exports) { + NODE_SET_METHOD(exports, "runInCallbackScope", RunInCallbackScope); +} + +} // namespace + +NODE_MODULE(binding, Initialize) diff --git a/test/addons/callback-scope/binding.gyp b/test/addons/callback-scope/binding.gyp new file mode 100644 index 00000000000000..7ede63d94a0d77 --- /dev/null +++ b/test/addons/callback-scope/binding.gyp @@ -0,0 +1,9 @@ +{ + 'targets': [ + { + 'target_name': 'binding', + 'defines': [ 'V8_DEPRECATION_WARNINGS=1' ], + 'sources': [ 'binding.cc' ] + } + ] +} diff --git a/test/addons/callback-scope/test-async-hooks.js b/test/addons/callback-scope/test-async-hooks.js new file mode 100644 index 00000000000000..94b53efc53d78e --- /dev/null +++ b/test/addons/callback-scope/test-async-hooks.js @@ -0,0 +1,22 @@ +'use strict'; + +const common = require('../../common'); +const assert = require('assert'); +const async_hooks = require('async_hooks'); +const { runInCallbackScope } = require(`./build/${common.buildType}/binding`); + +let insideHook = false; +async_hooks.createHook({ + before: common.mustCall((id) => { + assert.strictEqual(id, 1000); + insideHook = true; + }), + after: common.mustCall((id) => { + assert.strictEqual(id, 1000); + insideHook = false; + }) +}).enable(); + +runInCallbackScope({}, 1000, 1000, () => { + assert(insideHook); +}); diff --git a/test/addons/callback-scope/test.js b/test/addons/callback-scope/test.js new file mode 100644 index 00000000000000..2f2efe5f47b98a --- /dev/null +++ b/test/addons/callback-scope/test.js @@ -0,0 +1,17 @@ +'use strict'; + +const common = require('../../common'); +const assert = require('assert'); +const { runInCallbackScope } = require(`./build/${common.buildType}/binding`); + +assert.strictEqual(runInCallbackScope({}, 0, 0, () => 42), 42); + +{ + process.once('uncaughtException', common.mustCall((err) => { + assert.strictEqual(err.message, 'foo'); + })); + + runInCallbackScope({}, 0, 0, () => { + throw new Error('foo'); + }); +} From 4262fadbf7c3f2ed0d83690c013862df02f0c3a0 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Tue, 8 Aug 2017 20:08:08 +0200 Subject: [PATCH 4/8] src: remove virtually unused ExecScope MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/14697 Reviewed-By: Tobias Nießen Reviewed-By: Refael Ackermann Reviewed-By: James M Snell --- src/env-inl.h | 20 -------------------- src/env.h | 19 ------------------- src/node.cc | 3 ++- 3 files changed, 2 insertions(+), 40 deletions(-) diff --git a/src/env-inl.h b/src/env-inl.h index 93518fe5aefaa2..eddec5ca54fb48 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -194,26 +194,6 @@ inline Environment::AsyncHooks::InitScope::~InitScope() { env_->async_hooks()->pop_ids(uid_fields_ref_[AsyncHooks::kCurrentAsyncId]); } -inline Environment::AsyncHooks::ExecScope::ExecScope( - Environment* env, double async_id, double trigger_id) - : env_(env), - async_id_(async_id), - disposed_(false) { - CHECK_GE(async_id, -1); - CHECK_GE(trigger_id, -1); - env->async_hooks()->push_ids(async_id, trigger_id); -} - -inline Environment::AsyncHooks::ExecScope::~ExecScope() { - if (disposed_) return; - Dispose(); -} - -inline void Environment::AsyncHooks::ExecScope::Dispose() { - disposed_ = true; - env_->async_hooks()->pop_ids(async_id_); -} - inline Environment::AsyncCallbackScope::AsyncCallbackScope(Environment* env) : env_(env) { env_->makecallback_cntr_++; diff --git a/src/env.h b/src/env.h index dcaeefd226096c..315d4fdc1852a4 100644 --- a/src/env.h +++ b/src/env.h @@ -421,25 +421,6 @@ class Environment { DISALLOW_COPY_AND_ASSIGN(InitScope); }; - // Used to manage the stack of async and trigger ids as calls are made into - // JS. Mainly used in MakeCallback(). - class ExecScope { - public: - ExecScope() = delete; - explicit ExecScope(Environment* env, double async_id, double trigger_id); - ~ExecScope(); - void Dispose(); - - private: - Environment* env_; - double async_id_; - // Manually track if the destructor has run so it isn't accidentally run - // twice on RAII cleanup. - bool disposed_; - - DISALLOW_COPY_AND_ASSIGN(ExecScope); - }; - private: friend class Environment; // So we can call the constructor. inline explicit AsyncHooks(v8::Isolate* isolate); diff --git a/src/node.cc b/src/node.cc index cdbb7edb9eb0d7..3cfaf8d12944c8 100644 --- a/src/node.cc +++ b/src/node.cc @@ -4692,8 +4692,9 @@ inline int Start(Isolate* isolate, IsolateData* isolate_data, { Environment::AsyncCallbackScope callback_scope(&env); - Environment::AsyncHooks::ExecScope exec_scope(&env, 1, 0); + env.async_hooks()->push_ids(1, 0); LoadEnvironment(&env); + env.async_hooks()->pop_ids(1); } env.set_trace_sync_io(trace_sync_io); From 008d3bc165be47ddc1079ad9f556797df574e815 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Tue, 8 Aug 2017 20:18:16 +0200 Subject: [PATCH 5/8] n-api: use AsyncResource for Work tracking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enable combining N-API async work with async-hooks. PR-URL: https://github.com/nodejs/node/pull/14697 Reviewed-By: Tobias Nießen Reviewed-By: Refael Ackermann Reviewed-By: James M Snell Reviewed-By: Jason Ginchereau Reviewed-By: Michael Dawson --- doc/api/n-api.md | 20 +++++++ src/node_api.cc | 32 ++++++++-- src/node_api.h | 2 + .../test_async/test-async-hooks.js | 60 +++++++++++++++++++ test/addons-napi/test_async/test.js | 4 +- test/addons-napi/test_async/test_async.cc | 31 ++++++---- 6 files changed, 129 insertions(+), 20 deletions(-) create mode 100644 test/addons-napi/test_async/test-async-hooks.js diff --git a/doc/api/n-api.md b/doc/api/n-api.md index b9d06079fdba19..0a74d89ea012e0 100644 --- a/doc/api/n-api.md +++ b/doc/api/n-api.md @@ -3256,10 +3256,16 @@ callback invocation, even when it was cancelled. ### napi_create_async_work ```C NAPI_EXTERN napi_status napi_create_async_work(napi_env env, + napi_value async_resource, + const char* async_resource_name, napi_async_execute_callback execute, napi_async_complete_callback complete, void* data, @@ -3267,6 +3273,10 @@ napi_status napi_create_async_work(napi_env env, ``` - `[in] env`: The environment that the API is invoked under. +- `[in] async_resource`: An optional object associated with the async work + that will be passed to possible async_hooks [`init` hooks][]. +- `[in] async_resource_name`: An identifier for the kind of resource that is +being provided for diagnostic information exposed by the `async_hooks` API. - `[in] execute`: The native function which should be called to excute the logic asynchronously. - `[in] complete`: The native function which will be called when the @@ -3282,6 +3292,14 @@ This API allocates a work object that is used to execute logic asynchronously. It should be freed using [`napi_delete_async_work`][] once the work is no longer required. +`async_resource_name` should be a null-terminated, UTF-8-encoded string. + +*Note*: The `async_resource_name` identifier is provided by the user and should +be representative of the type of async work being performed. It is also +recommended to apply namespacing to the identifier, e.g. by including the +module name. See the [`async_hooks` documentation][async_hooks `type`] +for more information. + ### napi_delete_async_work