Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit d16e2fa

Browse filesBrowse files
legendecastargos
authored andcommitted
n-api: napi_make_callback emit async init with resource of async_context
instead of emit async init with receiver of the callback. PR-URL: #32930 Fixes: #32898 Reviewed-By: Stephen Belanger <admin@stephenbelanger.com> Reviewed-By: Michael Dawson <midawson@redhat.com> Reviewed-By: Gerhard Stöbich <deb2001-github@yahoo.de>
1 parent 7691b67 commit d16e2fa
Copy full SHA for d16e2fa

File tree

Expand file treeCollapse file tree

10 files changed

+465
-98
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

10 files changed

+465
-98
lines changed
Open diff view settings
Collapse file

‎doc/api/n-api.md‎

Copy file name to clipboardExpand all lines: doc/api/n-api.md
+26-10Lines changed: 26 additions & 10 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -5189,19 +5189,31 @@ napi_status napi_async_init(napi_env env,
51895189

51905190
* `[in] env`: The environment that the API is invoked under.
51915191
* `[in] async_resource`: Object associated with the async work
5192-
that will be passed to possible `async_hooks` [`init` hooks][].
5193-
In order to retain ABI compatibility with previous versions,
5194-
passing `NULL` for `async_resource` does not result in an error. However,
5195-
this results in incorrect operation of async hooks for the
5196-
napi_async_context created. Potential issues include
5197-
loss of async context when using the AsyncLocalStorage API.
5198-
* `[in] async_resource_name`: Identifier for the kind of resource
5199-
that is being provided for diagnostic information exposed by the
5200-
`async_hooks` API.
5192+
that will be passed to possible `async_hooks` [`init` hooks][] and can be
5193+
accessed by [`async_hooks.executionAsyncResource()`][].
5194+
* `[in] async_resource_name`: Identifier for the kind of resource that is being
5195+
provided for diagnostic information exposed by the `async_hooks` API.
52015196
* `[out] result`: The initialized async context.
52025197

52035198
Returns `napi_ok` if the API succeeded.
52045199

5200+
The `async_resource` object needs to be kept alive until
5201+
[`napi_async_destroy`][] to keep `async_hooks` related API acts correctly. In
5202+
order to retain ABI compatibility with previous versions, `napi_async_context`s
5203+
are not maintaining the strong reference to the `async_resource` objects to
5204+
avoid introducing causing memory leaks. However, if the `async_resource` is
5205+
garbage collected by JavaScript engine before the `napi_async_context` was
5206+
destroyed by `napi_async_destroy`, calling `napi_async_context` related APIs
5207+
like [`napi_open_callback_scope`][] and [`napi_make_callback`][] can cause
5208+
problems like loss of async context when using the `AsyncLocalStoage` API.
5209+
5210+
In order to retain ABI compatibility with previous versions, passing `NULL`
5211+
for `async_resource` does not result in an error. However, this is not
5212+
recommended as this will result poor results with `async_hooks`
5213+
[`init` hooks][] and `async_hooks.executionAsyncResource()` as the resource is
5214+
now required by the underlying `async_hooks` implementation in order to provide
5215+
the linkage between async callbacks.
5216+
52055217
### napi_async_destroy
52065218
<!-- YAML
52075219
added: v8.6.0
@@ -5288,7 +5300,9 @@ NAPI_EXTERN napi_status napi_open_callback_scope(napi_env env,
52885300

52895301
* `[in] env`: The environment that the API is invoked under.
52905302
* `[in] resource_object`: An object associated with the async work
5291-
that will be passed to possible `async_hooks` [`init` hooks][].
5303+
that will be passed to possible `async_hooks` [`init` hooks][]. This
5304+
parameter has been deprecated and is ignored at runtime. Use the
5305+
`async_resource` parameter in [`napi_async_init`][] instead.
52925306
* `[in] context`: Context for the async operation that is invoking the callback.
52935307
This should be a value previously obtained from [`napi_async_init`][].
52945308
* `[out] result`: The newly created scope.
@@ -5985,13 +5999,15 @@ This API may only be called from the main thread.
59855999
[`Number.MAX_SAFE_INTEGER`]: https://tc39.github.io/ecma262/#sec-number.max_safe_integer
59866000
[`Number.MIN_SAFE_INTEGER`]: https://tc39.github.io/ecma262/#sec-number.min_safe_integer
59876001
[`Worker`]: worker_threads.md#worker_threads_class_worker
6002+
[`async_hooks.executionAsyncResource()`]: async_hooks.md#async_hooks_async_hooks_executionasyncresource
59886003
[`global`]: globals.md#globals_global
59896004
[`init` hooks]: async_hooks.md#async_hooks_init_asyncid_type_triggerasyncid_resource
59906005
[`napi_add_async_cleanup_hook`]: #n_api_napi_add_async_cleanup_hook
59916006
[`napi_add_env_cleanup_hook`]: #n_api_napi_add_env_cleanup_hook
59926007
[`napi_add_finalizer`]: #n_api_napi_add_finalizer
59936008
[`napi_async_cleanup_hook`]: #n_api_napi_async_cleanup_hook
59946009
[`napi_async_complete_callback`]: #n_api_napi_async_complete_callback
6010+
[`napi_async_destroy`]: #n_api_napi_async_destroy
59956011
[`napi_async_init`]: #n_api_napi_async_init
59966012
[`napi_callback`]: #n_api_napi_callback
59976013
[`napi_cancel_async_work`]: #n_api_napi_cancel_async_work
Collapse file

‎src/node_api.cc‎

Copy file name to clipboardExpand all lines: src/node_api.cc
+143-44Lines changed: 143 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1+
#include "async_wrap-inl.h"
12
#include "env-inl.h"
23
#define NAPI_EXPERIMENTAL
34
#include "js_native_api_v8.h"
5+
#include "memory_tracker-inl.h"
46
#include "node_api.h"
57
#include "node_binding.h"
68
#include "node_buffer.h"
79
#include "node_errors.h"
810
#include "node_internals.h"
911
#include "threadpoolwork-inl.h"
12+
#include "tracing/traced_value.h"
1013
#include "util-inl.h"
1114

1215
#include <memory>
@@ -104,16 +107,6 @@ static inline napi_env NewEnv(v8::Local<v8::Context> context) {
104107
return result;
105108
}
106109

107-
static inline napi_callback_scope
108-
JsCallbackScopeFromV8CallbackScope(node::CallbackScope* s) {
109-
return reinterpret_cast<napi_callback_scope>(s);
110-
}
111-
112-
static inline node::CallbackScope*
113-
V8CallbackScopeFromJsCallbackScope(napi_callback_scope s) {
114-
return reinterpret_cast<node::CallbackScope*>(s);
115-
}
116-
117110
static inline void trigger_fatal_exception(
118111
napi_env env, v8::Local<v8::Value> local_err) {
119112
v8::Local<v8::Message> local_msg =
@@ -435,6 +428,111 @@ class ThreadSafeFunction : public node::AsyncResource {
435428
bool handles_closing;
436429
};
437430

431+
/**
432+
* Compared to node::AsyncResource, the resource object in AsyncContext is
433+
* gc-able. AsyncContext holds a weak reference to the resource object.
434+
* AsyncContext::MakeCallback doesn't implicitly set the receiver of the
435+
* callback to the resource object.
436+
*/
437+
class AsyncContext {
438+
public:
439+
AsyncContext(node_napi_env env,
440+
v8::Local<v8::Object> resource_object,
441+
const v8::Local<v8::String> resource_name,
442+
bool externally_managed_resource)
443+
: env_(env) {
444+
async_id_ = node_env()->new_async_id();
445+
trigger_async_id_ = node_env()->get_default_trigger_async_id();
446+
resource_.Reset(node_env()->isolate(), resource_object);
447+
lost_reference_ = false;
448+
if (externally_managed_resource) {
449+
resource_.SetWeak(
450+
this, AsyncContext::WeakCallback, v8::WeakCallbackType::kParameter);
451+
}
452+
453+
node::AsyncWrap::EmitAsyncInit(node_env(),
454+
resource_object,
455+
resource_name,
456+
async_id_,
457+
trigger_async_id_);
458+
}
459+
460+
~AsyncContext() {
461+
resource_.Reset();
462+
lost_reference_ = true;
463+
node::AsyncWrap::EmitDestroy(node_env(), async_id_);
464+
}
465+
466+
inline v8::MaybeLocal<v8::Value> MakeCallback(
467+
v8::Local<v8::Object> recv,
468+
const v8::Local<v8::Function> callback,
469+
int argc,
470+
v8::Local<v8::Value> argv[]) {
471+
EnsureReference();
472+
return node::InternalMakeCallback(node_env(),
473+
resource(),
474+
recv,
475+
callback,
476+
argc,
477+
argv,
478+
{async_id_, trigger_async_id_});
479+
}
480+
481+
inline napi_callback_scope OpenCallbackScope() {
482+
EnsureReference();
483+
napi_callback_scope it =
484+
reinterpret_cast<napi_callback_scope>(new CallbackScope(this));
485+
env_->open_callback_scopes++;
486+
return it;
487+
}
488+
489+
inline void EnsureReference() {
490+
if (lost_reference_) {
491+
const v8::HandleScope handle_scope(node_env()->isolate());
492+
resource_.Reset(node_env()->isolate(),
493+
v8::Object::New(node_env()->isolate()));
494+
lost_reference_ = false;
495+
}
496+
}
497+
498+
inline node::Environment* node_env() { return env_->node_env(); }
499+
inline v8::Local<v8::Object> resource() {
500+
return resource_.Get(node_env()->isolate());
501+
}
502+
inline node::async_context async_context() {
503+
return {async_id_, trigger_async_id_};
504+
}
505+
506+
static inline void CloseCallbackScope(node_napi_env env,
507+
napi_callback_scope s) {
508+
CallbackScope* callback_scope = reinterpret_cast<CallbackScope*>(s);
509+
delete callback_scope;
510+
env->open_callback_scopes--;
511+
}
512+
513+
static void WeakCallback(const v8::WeakCallbackInfo<AsyncContext>& data) {
514+
AsyncContext* async_context = data.GetParameter();
515+
async_context->resource_.Reset();
516+
async_context->lost_reference_ = true;
517+
}
518+
519+
private:
520+
class CallbackScope : public node::CallbackScope {
521+
public:
522+
explicit CallbackScope(AsyncContext* async_context)
523+
: node::CallbackScope(async_context->node_env()->isolate(),
524+
async_context->resource_.Get(
525+
async_context->node_env()->isolate()),
526+
async_context->async_context()) {}
527+
};
528+
529+
node_napi_env env_;
530+
double async_id_;
531+
double trigger_async_id_;
532+
v8::Global<v8::Object> resource_;
533+
bool lost_reference_;
534+
};
535+
438536
} // end of anonymous namespace
439537

440538
} // end of namespace v8impl
@@ -627,28 +725,19 @@ NAPI_NO_RETURN void napi_fatal_error(const char* location,
627725
}
628726

629727
napi_status napi_open_callback_scope(napi_env env,
630-
napi_value resource_object,
728+
napi_value /** ignored */,
631729
napi_async_context async_context_handle,
632730
napi_callback_scope* result) {
633731
// Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw
634732
// JS exceptions.
635733
CHECK_ENV(env);
636734
CHECK_ARG(env, result);
637735

638-
v8::Local<v8::Context> context = env->context();
639-
640-
node::async_context* node_async_context =
641-
reinterpret_cast<node::async_context*>(async_context_handle);
642-
643-
v8::Local<v8::Object> resource;
644-
CHECK_TO_OBJECT(env, context, resource, resource_object);
736+
v8impl::AsyncContext* node_async_context =
737+
reinterpret_cast<v8impl::AsyncContext*>(async_context_handle);
645738

646-
*result = v8impl::JsCallbackScopeFromV8CallbackScope(
647-
new node::CallbackScope(env->isolate,
648-
resource,
649-
*node_async_context));
739+
*result = node_async_context->OpenCallbackScope();
650740

651-
env->open_callback_scopes++;
652741
return napi_clear_last_error(env);
653742
}
654743

@@ -661,8 +750,9 @@ napi_status napi_close_callback_scope(napi_env env, napi_callback_scope scope) {
661750
return napi_callback_scope_mismatch;
662751
}
663752

664-
env->open_callback_scopes--;
665-
delete v8impl::V8CallbackScopeFromJsCallbackScope(scope);
753+
v8impl::AsyncContext::CloseCallbackScope(reinterpret_cast<node_napi_env>(env),
754+
scope);
755+
666756
return napi_clear_last_error(env);
667757
}
668758

@@ -678,20 +768,24 @@ napi_status napi_async_init(napi_env env,
678768
v8::Local<v8::Context> context = env->context();
679769

680770
v8::Local<v8::Object> v8_resource;
771+
bool externally_managed_resource;
681772
if (async_resource != nullptr) {
682773
CHECK_TO_OBJECT(env, context, v8_resource, async_resource);
774+
externally_managed_resource = true;
683775
} else {
684776
v8_resource = v8::Object::New(isolate);
777+
externally_managed_resource = false;
685778
}
686779

687780
v8::Local<v8::String> v8_resource_name;
688781
CHECK_TO_STRING(env, context, v8_resource_name, async_resource_name);
689782

690-
// TODO(jasongin): Consider avoiding allocation here by using
691-
// a tagged pointer with 2×31 bit fields instead.
692-
node::async_context* async_context = new node::async_context();
783+
auto async_context =
784+
new v8impl::AsyncContext(reinterpret_cast<node_napi_env>(env),
785+
v8_resource,
786+
v8_resource_name,
787+
externally_managed_resource);
693788

694-
*async_context = node::EmitAsyncInit(isolate, v8_resource, v8_resource_name);
695789
*result = reinterpret_cast<napi_async_context>(async_context);
696790

697791
return napi_clear_last_error(env);
@@ -702,11 +796,8 @@ napi_status napi_async_destroy(napi_env env,
702796
CHECK_ENV(env);
703797
CHECK_ARG(env, async_context);
704798

705-
node::async_context* node_async_context =
706-
reinterpret_cast<node::async_context*>(async_context);
707-
node::EmitAsyncDestroy(
708-
reinterpret_cast<node_napi_env>(env)->node_env(),
709-
*node_async_context);
799+
v8impl::AsyncContext* node_async_context =
800+
reinterpret_cast<v8impl::AsyncContext*>(async_context);
710801

711802
delete node_async_context;
712803

@@ -734,17 +825,25 @@ napi_status napi_make_callback(napi_env env,
734825
v8::Local<v8::Function> v8func;
735826
CHECK_TO_FUNCTION(env, v8func, func);
736827

737-
node::async_context* node_async_context =
738-
reinterpret_cast<node::async_context*>(async_context);
739-
if (node_async_context == nullptr) {
740-
static node::async_context empty_context = { 0, 0 };
741-
node_async_context = &empty_context;
742-
}
828+
v8::MaybeLocal<v8::Value> callback_result;
743829

744-
v8::MaybeLocal<v8::Value> callback_result = node::MakeCallback(
745-
env->isolate, v8recv, v8func, argc,
746-
reinterpret_cast<v8::Local<v8::Value>*>(const_cast<napi_value*>(argv)),
747-
*node_async_context);
830+
if (async_context == nullptr) {
831+
callback_result = node::MakeCallback(
832+
env->isolate,
833+
v8recv,
834+
v8func,
835+
argc,
836+
reinterpret_cast<v8::Local<v8::Value>*>(const_cast<napi_value*>(argv)),
837+
{0, 0});
838+
} else {
839+
auto node_async_context =
840+
reinterpret_cast<v8impl::AsyncContext*>(async_context);
841+
callback_result = node_async_context->MakeCallback(
842+
v8recv,
843+
v8func,
844+
argc,
845+
reinterpret_cast<v8::Local<v8::Value>*>(const_cast<napi_value*>(argv)));
846+
}
748847

749848
if (try_catch.HasCaught()) {
750849
return napi_set_last_error(env, napi_pending_exception);

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.