-
-
Notifications
You must be signed in to change notification settings - Fork 33.9k
n-api: napi_make_callback emit async init event with resource of async_context #32930
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -5189,19 +5189,31 @@ napi_status napi_async_init(napi_env env, | |||||||||||||||
|
|
||||||||||||||||
| * `[in] env`: The environment that the API is invoked under. | ||||||||||||||||
| * `[in] async_resource`: Object associated with the async work | ||||||||||||||||
| that will be passed to possible `async_hooks` [`init` hooks][]. | ||||||||||||||||
| In order to retain ABI compatibility with previous versions, | ||||||||||||||||
| passing `NULL` for `async_resource` does not result in an error. However, | ||||||||||||||||
| this results in incorrect operation of async hooks for the | ||||||||||||||||
| napi_async_context created. Potential issues include | ||||||||||||||||
| loss of async context when using the AsyncLocalStorage API. | ||||||||||||||||
| * `[in] async_resource_name`: Identifier for the kind of resource | ||||||||||||||||
| that is being provided for diagnostic information exposed by the | ||||||||||||||||
| `async_hooks` API. | ||||||||||||||||
| that will be passed to possible `async_hooks` [`init` hooks][] and can be | ||||||||||||||||
| accessed by [`async_hooks.executionAsyncResource()`][]. | ||||||||||||||||
| * `[in] async_resource_name`: Identifier for the kind of resource that is being | ||||||||||||||||
| provided for diagnostic information exposed by the `async_hooks` API. | ||||||||||||||||
| * `[out] result`: The initialized async context. | ||||||||||||||||
|
|
||||||||||||||||
| Returns `napi_ok` if the API succeeded. | ||||||||||||||||
|
|
||||||||||||||||
| The `async_resource` object needs to be kept alive until | ||||||||||||||||
| [`napi_async_destroy`][] to keep `async_hooks` related API acts correctly. In | ||||||||||||||||
| order to retain ABI compatibility with previous versions, `napi_async_context`s | ||||||||||||||||
| are not maintaining the strong reference to the `async_resource` objects to | ||||||||||||||||
| avoid introducing causing memory leaks. However, if the `async_resource` is | ||||||||||||||||
| garbage collected by JavaScript engine before the `napi_async_context` was | ||||||||||||||||
| destroyed by `napi_async_destroy`, calling `napi_async_context` related APIs | ||||||||||||||||
| like [`napi_open_callback_scope`][] and [`napi_make_callback`][] can cause | ||||||||||||||||
| problems like loss of async context when using the `AsyncLocalStoage` API. | ||||||||||||||||
|
|
||||||||||||||||
| In order to retain ABI compatibility with previous versions, passing `NULL` | ||||||||||||||||
| for `async_resource` does not result in an error. However, this is not | ||||||||||||||||
| recommended as this will result poor results with `async_hooks` | ||||||||||||||||
| [`init` hooks][] and `async_hooks.executionAsyncResource()` as the resource is | ||||||||||||||||
| now required by the underlying `async_hooks` implementation in order to provide | ||||||||||||||||
| the linkage between async callbacks. | ||||||||||||||||
|
|
||||||||||||||||
|
||||||||||||||||
| In order to retain ABI compatibility with previous versions, passing `NULL` | |
| for `async_resource` does not result in an error. However, this is not | |
| recommended as this will result poor results with `async_hooks` [`init` hooks][] | |
| and `async_hooks.executionAsyncResource()` as the resource is now required | |
| by the underlying async_hooks implementation in order to provide the linkage | |
| between calls. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,15 @@ | ||
| #include "async_wrap-inl.h" | ||
| #include "env-inl.h" | ||
| #define NAPI_EXPERIMENTAL | ||
| #include "js_native_api_v8.h" | ||
| #include "memory_tracker-inl.h" | ||
| #include "node_api.h" | ||
| #include "node_binding.h" | ||
| #include "node_buffer.h" | ||
| #include "node_errors.h" | ||
| #include "node_internals.h" | ||
| #include "threadpoolwork-inl.h" | ||
| #include "tracing/traced_value.h" | ||
| #include "util-inl.h" | ||
|
|
||
| #include <memory> | ||
|
|
@@ -104,16 +107,6 @@ static inline napi_env NewEnv(v8::Local<v8::Context> context) { | |
| return result; | ||
| } | ||
|
|
||
| static inline napi_callback_scope | ||
| JsCallbackScopeFromV8CallbackScope(node::CallbackScope* s) { | ||
| return reinterpret_cast<napi_callback_scope>(s); | ||
| } | ||
|
|
||
| static inline node::CallbackScope* | ||
| V8CallbackScopeFromJsCallbackScope(napi_callback_scope s) { | ||
| return reinterpret_cast<node::CallbackScope*>(s); | ||
| } | ||
|
|
||
| static inline void trigger_fatal_exception( | ||
| napi_env env, v8::Local<v8::Value> local_err) { | ||
| v8::Local<v8::Message> local_msg = | ||
|
|
@@ -435,6 +428,111 @@ class ThreadSafeFunction : public node::AsyncResource { | |
| bool handles_closing; | ||
| }; | ||
|
|
||
| /** | ||
| * Compared to node::AsyncResource, the resource object in AsyncContext is | ||
| * gc-able. AsyncContext holds a weak reference to the resource object. | ||
| * AsyncContext::MakeCallback doesn't implicitly set the receiver of the | ||
| * callback to the resource object. | ||
| */ | ||
| class AsyncContext { | ||
| public: | ||
| AsyncContext(node_napi_env env, | ||
| v8::Local<v8::Object> resource_object, | ||
| const v8::Local<v8::String> resource_name, | ||
| bool externally_managed_resource) | ||
| : env_(env) { | ||
| async_id_ = node_env()->new_async_id(); | ||
| trigger_async_id_ = node_env()->get_default_trigger_async_id(); | ||
| resource_.Reset(node_env()->isolate(), resource_object); | ||
| lost_reference_ = false; | ||
| if (externally_managed_resource) { | ||
| resource_.SetWeak( | ||
| this, AsyncContext::WeakCallback, v8::WeakCallbackType::kParameter); | ||
| } | ||
|
|
||
| node::AsyncWrap::EmitAsyncInit(node_env(), | ||
| resource_object, | ||
| resource_name, | ||
| async_id_, | ||
| trigger_async_id_); | ||
| } | ||
|
|
||
| ~AsyncContext() { | ||
| resource_.Reset(); | ||
| lost_reference_ = true; | ||
| node::AsyncWrap::EmitDestroy(node_env(), async_id_); | ||
| } | ||
|
|
||
| inline v8::MaybeLocal<v8::Value> MakeCallback( | ||
| v8::Local<v8::Object> recv, | ||
| const v8::Local<v8::Function> callback, | ||
| int argc, | ||
| v8::Local<v8::Value> argv[]) { | ||
| EnsureReference(); | ||
| return node::InternalMakeCallback(node_env(), | ||
| resource(), | ||
| recv, | ||
| callback, | ||
| argc, | ||
| argv, | ||
| {async_id_, trigger_async_id_}); | ||
| } | ||
|
|
||
| inline napi_callback_scope OpenCallbackScope() { | ||
| EnsureReference(); | ||
| napi_callback_scope it = | ||
| reinterpret_cast<napi_callback_scope>(new CallbackScope(this)); | ||
| env_->open_callback_scopes++; | ||
| return it; | ||
| } | ||
|
|
||
| inline void EnsureReference() { | ||
| if (lost_reference_) { | ||
| const v8::HandleScope handle_scope(node_env()->isolate()); | ||
| resource_.Reset(node_env()->isolate(), | ||
| v8::Object::New(node_env()->isolate())); | ||
| lost_reference_ = false; | ||
| } | ||
| } | ||
|
|
||
| inline node::Environment* node_env() { return env_->node_env(); } | ||
| inline v8::Local<v8::Object> resource() { | ||
| return resource_.Get(node_env()->isolate()); | ||
| } | ||
| inline node::async_context async_context() { | ||
| return {async_id_, trigger_async_id_}; | ||
| } | ||
|
|
||
| static inline void CloseCallbackScope(node_napi_env env, | ||
| napi_callback_scope s) { | ||
| CallbackScope* callback_scope = reinterpret_cast<CallbackScope*>(s); | ||
| delete callback_scope; | ||
| env->open_callback_scopes--; | ||
| } | ||
|
|
||
| static void WeakCallback(const v8::WeakCallbackInfo<AsyncContext>& data) { | ||
| AsyncContext* async_context = data.GetParameter(); | ||
| async_context->resource_.Reset(); | ||
| async_context->lost_reference_ = true; | ||
| } | ||
|
|
||
| private: | ||
| class CallbackScope : public node::CallbackScope { | ||
| public: | ||
| explicit CallbackScope(AsyncContext* async_context) | ||
| : node::CallbackScope(async_context->node_env()->isolate(), | ||
| async_context->resource_.Get( | ||
| async_context->node_env()->isolate()), | ||
| async_context->async_context()) {} | ||
| }; | ||
|
|
||
| node_napi_env env_; | ||
| double async_id_; | ||
| double trigger_async_id_; | ||
| v8::Global<v8::Object> resource_; | ||
| bool lost_reference_; | ||
| }; | ||
legendecas marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| } // end of anonymous namespace | ||
|
|
||
| } // end of namespace v8impl | ||
|
|
@@ -627,28 +725,19 @@ NAPI_NO_RETURN void napi_fatal_error(const char* location, | |
| } | ||
|
|
||
| napi_status napi_open_callback_scope(napi_env env, | ||
| napi_value resource_object, | ||
| napi_value /** ignored */, | ||
|
||
| napi_async_context async_context_handle, | ||
| napi_callback_scope* result) { | ||
| // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw | ||
| // JS exceptions. | ||
| CHECK_ENV(env); | ||
| CHECK_ARG(env, result); | ||
|
|
||
| v8::Local<v8::Context> context = env->context(); | ||
|
|
||
| node::async_context* node_async_context = | ||
| reinterpret_cast<node::async_context*>(async_context_handle); | ||
|
|
||
| v8::Local<v8::Object> resource; | ||
| CHECK_TO_OBJECT(env, context, resource, resource_object); | ||
| v8impl::AsyncContext* node_async_context = | ||
| reinterpret_cast<v8impl::AsyncContext*>(async_context_handle); | ||
|
|
||
| *result = v8impl::JsCallbackScopeFromV8CallbackScope( | ||
| new node::CallbackScope(env->isolate, | ||
| resource, | ||
| *node_async_context)); | ||
| *result = node_async_context->OpenCallbackScope(); | ||
|
|
||
| env->open_callback_scopes++; | ||
| return napi_clear_last_error(env); | ||
| } | ||
|
|
||
|
|
@@ -661,8 +750,9 @@ napi_status napi_close_callback_scope(napi_env env, napi_callback_scope scope) { | |
| return napi_callback_scope_mismatch; | ||
| } | ||
|
|
||
| env->open_callback_scopes--; | ||
| delete v8impl::V8CallbackScopeFromJsCallbackScope(scope); | ||
| v8impl::AsyncContext::CloseCallbackScope(reinterpret_cast<node_napi_env>(env), | ||
| scope); | ||
|
|
||
| return napi_clear_last_error(env); | ||
| } | ||
|
|
||
|
|
@@ -678,20 +768,24 @@ napi_status napi_async_init(napi_env env, | |
| v8::Local<v8::Context> context = env->context(); | ||
|
|
||
| v8::Local<v8::Object> v8_resource; | ||
| bool externally_managed_resource; | ||
| if (async_resource != nullptr) { | ||
| CHECK_TO_OBJECT(env, context, v8_resource, async_resource); | ||
| externally_managed_resource = true; | ||
| } else { | ||
| v8_resource = v8::Object::New(isolate); | ||
| externally_managed_resource = false; | ||
| } | ||
|
|
||
| v8::Local<v8::String> v8_resource_name; | ||
| CHECK_TO_STRING(env, context, v8_resource_name, async_resource_name); | ||
|
|
||
| // TODO(jasongin): Consider avoiding allocation here by using | ||
| // a tagged pointer with 2×31 bit fields instead. | ||
| node::async_context* async_context = new node::async_context(); | ||
| auto async_context = | ||
| new v8impl::AsyncContext(reinterpret_cast<node_napi_env>(env), | ||
| v8_resource, | ||
| v8_resource_name, | ||
| externally_managed_resource); | ||
|
|
||
| *async_context = node::EmitAsyncInit(isolate, v8_resource, v8_resource_name); | ||
| *result = reinterpret_cast<napi_async_context>(async_context); | ||
|
|
||
| return napi_clear_last_error(env); | ||
|
|
@@ -702,11 +796,8 @@ napi_status napi_async_destroy(napi_env env, | |
| CHECK_ENV(env); | ||
| CHECK_ARG(env, async_context); | ||
|
|
||
| node::async_context* node_async_context = | ||
| reinterpret_cast<node::async_context*>(async_context); | ||
| node::EmitAsyncDestroy( | ||
| reinterpret_cast<node_napi_env>(env)->node_env(), | ||
| *node_async_context); | ||
| v8impl::AsyncContext* node_async_context = | ||
| reinterpret_cast<v8impl::AsyncContext*>(async_context); | ||
|
|
||
| delete node_async_context; | ||
|
|
||
|
|
@@ -734,17 +825,25 @@ napi_status napi_make_callback(napi_env env, | |
| v8::Local<v8::Function> v8func; | ||
| CHECK_TO_FUNCTION(env, v8func, func); | ||
|
|
||
| node::async_context* node_async_context = | ||
| reinterpret_cast<node::async_context*>(async_context); | ||
| if (node_async_context == nullptr) { | ||
| static node::async_context empty_context = { 0, 0 }; | ||
| node_async_context = &empty_context; | ||
| } | ||
| v8::MaybeLocal<v8::Value> callback_result; | ||
|
|
||
| v8::MaybeLocal<v8::Value> callback_result = node::MakeCallback( | ||
| env->isolate, v8recv, v8func, argc, | ||
| reinterpret_cast<v8::Local<v8::Value>*>(const_cast<napi_value*>(argv)), | ||
| *node_async_context); | ||
| if (async_context == nullptr) { | ||
| callback_result = node::MakeCallback( | ||
| env->isolate, | ||
| v8recv, | ||
| v8func, | ||
| argc, | ||
| reinterpret_cast<v8::Local<v8::Value>*>(const_cast<napi_value*>(argv)), | ||
| {0, 0}); | ||
| } else { | ||
| auto node_async_context = | ||
| reinterpret_cast<v8impl::AsyncContext*>(async_context); | ||
| callback_result = node_async_context->MakeCallback( | ||
| v8recv, | ||
| v8func, | ||
| argc, | ||
| reinterpret_cast<v8::Local<v8::Value>*>(const_cast<napi_value*>(argv))); | ||
| } | ||
|
|
||
| if (try_catch.HasCaught()) { | ||
| return napi_set_last_error(env, napi_pending_exception); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.