diff options
author | Anna Henningsen <anna@addaleax.net> | 2017-08-08 20:02:55 +0200 |
---|---|---|
committer | Anna Henningsen <anna@addaleax.net> | 2017-09-14 17:38:44 +0200 |
commit | 64616bb76932f8a1b05cc616e82dba84c2a7f87e (patch) | |
tree | 9cf828e999cc751321ac01c170273f0f543fa4d5 /src | |
parent | be6d807bbf09ab09cfabf7e941fbaedbb1ff7456 (diff) | |
download | android-node-v8-64616bb76932f8a1b05cc616e82dba84c2a7f87e.tar.gz android-node-v8-64616bb76932f8a1b05cc616e82dba84c2a7f87e.tar.bz2 android-node-v8-64616bb76932f8a1b05cc616e82dba84c2a7f87e.zip |
src: refactor async callback handling
- 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 <tniessen@tnie.de>
Reviewed-By: Refael Ackermann <refack@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/async-wrap.cc | 69 | ||||
-rw-r--r-- | src/node.cc | 169 | ||||
-rw-r--r-- | src/node.h | 40 | ||||
-rw-r--r-- | src/node_internals.h | 8 |
4 files changed, 169 insertions, 117 deletions
diff --git a/src/async-wrap.cc b/src/async-wrap.cc index 391a684759..fe53d27994 100644 --- a/src/async-wrap.cc +++ b/src/async-wrap.cc @@ -657,73 +657,8 @@ void AsyncWrap::EmitAsyncInit(Environment* env, MaybeLocal<Value> AsyncWrap::MakeCallback(const Local<Function> cb, int argc, Local<Value>* 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<Value> 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<Object> process = env()->process_object(); - - if (tick_info->length() == 0) { - tick_info->set_index(0); - return ret; - } - - MaybeLocal<Value> 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<Value>() : 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 30cbb11efa..cdbb7edb9e 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> 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<v8::Object> object_; + Environment::AsyncCallbackScope callback_scope_; + bool failed_ = false; + bool pushed_ids_ = false; + bool closed_ = false; +}; -MaybeLocal<Value> MakeCallback(Environment* env, - Local<Value> recv, - const Local<Function> callback, - int argc, - Local<Value> 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> object, + async_context asyncContext) + : private_(new InternalCallbackScope(Environment::GetCurrent(isolate), + object, + asyncContext)), + try_catch_(isolate) { + try_catch_.SetVerbose(true); +} - Local<Object> 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> object, + const async_context& asyncContext) + : env_(env), + async_context_(asyncContext), + object_(object), + callback_scope_(env) { + CHECK(!object.IsEmpty()); - if (recv->IsObject()) { - object = recv.As<Object>(); - } + // 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<Value> 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<Object> process = env->process_object(); + Local<Object> 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<Value> InternalMakeCallback(Environment* env, + Local<Object> recv, + const Local<Function> callback, + int argc, + Local<Value> argv[], + async_context asyncContext) { + InternalCallbackScope scope(env, recv, asyncContext); + if (scope.Failed()) { + return Undefined(env->isolate()); + } + + MaybeLocal<Value> 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<Value> 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<Value>(), callback, argc, argv, - asyncContext); + return InternalMakeCallback(env, recv, callback, + argc, argv, asyncContext); } diff --git a/src/node.h b/src/node.h index 5fe7bde428..6558a9ee6d 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<v8::Object> 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<v8::Object> resource_; diff --git a/src/node_internals.h b/src/node_internals.h index 9b6fae9d6a..9371d442ad 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -286,6 +286,14 @@ static v8::MaybeLocal<v8::Object> New(Environment* env, } } // namespace Buffer +v8::MaybeLocal<v8::Value> InternalMakeCallback( + Environment* env, + v8::Local<v8::Object> recv, + const v8::Local<v8::Function> callback, + int argc, + v8::Local<v8::Value> argv[], + async_context asyncContext); + } // namespace node #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS |