Skip to content

Commit 85eef92

Browse files
committed
src: always use strong reference to napi_async_context resource
There already is an existing requirement to have matching calls of `napi_async_init()` and `napi_async_destroy()`, so expecting users of this API to manually hold onto the resource for the duration of the `napi_async_context`'s lifetime is unnecessary. Weak callbacks are generally useful for when a corresponding C++ object should be cleaned up when a JS object is gargbage-collected, but that is not the case here.
1 parent 7c19d1c commit 85eef92

File tree

2 files changed

+18
-54
lines changed

2 files changed

+18
-54
lines changed

doc/api/n-api.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5924,6 +5924,10 @@ the runtime.
59245924
<!-- YAML
59255925
added: v8.6.0
59265926
napiVersion: 1
5927+
changes:
5928+
- version: REPLACEME
5929+
pr-url: https://github.com/nodejs/node/pull/59828
5930+
description: The `async_resource` object will not be held as a strong reference.
59275931
-->
59285932

59295933
```c
@@ -5943,23 +5947,19 @@ napi_status napi_async_init(napi_env env,
59435947

59445948
Returns `napi_ok` if the API succeeded.
59455949

5946-
The `async_resource` object needs to be kept alive until
5947-
[`napi_async_destroy`][] to keep `async_hooks` related API acts correctly. In
5948-
order to retain ABI compatibility with previous versions, `napi_async_context`s
5949-
are not maintaining the strong reference to the `async_resource` objects to
5950-
avoid introducing causing memory leaks. However, if the `async_resource` is
5951-
garbage collected by JavaScript engine before the `napi_async_context` was
5952-
destroyed by `napi_async_destroy`, calling `napi_async_context` related APIs
5953-
like [`napi_open_callback_scope`][] and [`napi_make_callback`][] can cause
5954-
problems like loss of async context when using the `AsyncLocalStorage` API.
5955-
59565950
In order to retain ABI compatibility with previous versions, passing `NULL`
59575951
for `async_resource` does not result in an error. However, this is not
59585952
recommended as this will result in undesirable behavior with `async_hooks`
59595953
[`init` hooks][] and `async_hooks.executionAsyncResource()` as the resource is
59605954
now required by the underlying `async_hooks` implementation in order to provide
59615955
the linkage between async callbacks.
59625956

5957+
Previous versions of this API were not maintaining a strong reference to
5958+
`async_resource` while the `napi_async_context` object existed and instead
5959+
expected the caller to hold a strong reference. This has been changed, as a
5960+
corresponding call to [`napi_async_destroy`][] for every call to
5961+
`napi_async_init()` is a requirement in any case to avoid memory leaks.
5962+
59635963
### `napi_async_destroy`
59645964

59655965
<!-- YAML

src/node_api.cc

Lines changed: 8 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -538,19 +538,13 @@ class AsyncContext {
538538
public:
539539
AsyncContext(node_napi_env env,
540540
v8::Local<v8::Object> resource_object,
541-
const v8::Local<v8::String> resource_name,
542-
bool externally_managed_resource)
541+
v8::Local<v8::String> resource_name)
543542
: env_(env) {
544543
async_id_ = node_env()->new_async_id();
545544
trigger_async_id_ = node_env()->get_default_trigger_async_id();
546545
v8::Isolate* isolate = node_env()->isolate();
547546
resource_.Reset(isolate, resource_object);
548547
context_frame_.Reset(isolate, node::async_context_frame::current(isolate));
549-
lost_reference_ = false;
550-
if (externally_managed_resource) {
551-
resource_.SetWeak(
552-
this, AsyncContext::WeakCallback, v8::WeakCallbackType::kParameter);
553-
}
554548

555549
node::AsyncWrap::EmitAsyncInit(node_env(),
556550
resource_object,
@@ -559,18 +553,13 @@ class AsyncContext {
559553
trigger_async_id_);
560554
}
561555

562-
~AsyncContext() {
563-
resource_.Reset();
564-
lost_reference_ = true;
565-
node::AsyncWrap::EmitDestroy(node_env(), async_id_);
566-
}
556+
~AsyncContext() { node::AsyncWrap::EmitDestroy(node_env(), async_id_); }
567557

568558
inline v8::MaybeLocal<v8::Value> MakeCallback(
569559
v8::Local<v8::Object> recv,
570560
const v8::Local<v8::Function> callback,
571561
int argc,
572562
v8::Local<v8::Value> argv[]) {
573-
EnsureReference();
574563
return node::InternalMakeCallback(
575564
node_env(),
576565
resource(),
@@ -583,21 +572,11 @@ class AsyncContext {
583572
}
584573

585574
inline napi_callback_scope OpenCallbackScope() {
586-
EnsureReference();
587575
auto scope = new HeapAllocatedCallbackScope(this);
588576
env_->open_callback_scopes++;
589577
return scope->to_opaque();
590578
}
591579

592-
inline void EnsureReference() {
593-
if (lost_reference_) {
594-
const v8::HandleScope handle_scope(node_env()->isolate());
595-
resource_.Reset(node_env()->isolate(),
596-
v8::Object::New(node_env()->isolate()));
597-
lost_reference_ = false;
598-
}
599-
}
600-
601580
inline node::Environment* node_env() { return env_->node_env(); }
602581
inline v8::Local<v8::Object> resource() {
603582
return resource_.Get(node_env()->isolate());
@@ -609,15 +588,10 @@ class AsyncContext {
609588
static inline void CloseCallbackScope(node_napi_env env,
610589
napi_callback_scope s) {
611590
delete HeapAllocatedCallbackScope::FromOpaque(s);
591+
CHECK_GT(env->open_callback_scopes, 0);
612592
env->open_callback_scopes--;
613593
}
614594

615-
static void WeakCallback(const v8::WeakCallbackInfo<AsyncContext>& data) {
616-
AsyncContext* async_context = data.GetParameter();
617-
async_context->resource_.Reset();
618-
async_context->lost_reference_ = true;
619-
}
620-
621595
private:
622596
class HeapAllocatedCallbackScope final {
623597
public:
@@ -629,15 +603,11 @@ class AsyncContext {
629603
}
630604

631605
explicit HeapAllocatedCallbackScope(AsyncContext* async_context)
632-
: resource_storage_(async_context->node_env()->isolate(),
633-
async_context->resource_.Get(
634-
async_context->node_env()->isolate())),
635-
cs_(async_context->node_env(),
636-
&resource_storage_,
637-
async_context->async_context()) {}
606+
cs_(async_context->node_env(),
607+
&async_context->resource_storage_,
608+
async_context->async_context()) {}
638609

639610
private:
640-
v8::Global<v8::Object> resource_storage_;
641611
node::CallbackScope cs_;
642612
};
643613

@@ -943,23 +913,17 @@ napi_status NAPI_CDECL napi_async_init(napi_env env,
943913
v8::Local<v8::Context> context = env->context();
944914

945915
v8::Local<v8::Object> v8_resource;
946-
bool externally_managed_resource;
947916
if (async_resource != nullptr) {
948917
CHECK_TO_OBJECT(env, context, v8_resource, async_resource);
949-
externally_managed_resource = true;
950918
} else {
951919
v8_resource = v8::Object::New(isolate);
952-
externally_managed_resource = false;
953920
}
954921

955922
v8::Local<v8::String> v8_resource_name;
956923
CHECK_TO_STRING(env, context, v8_resource_name, async_resource_name);
957924

958-
v8impl::AsyncContext* async_context =
959-
new v8impl::AsyncContext(reinterpret_cast<node_napi_env>(env),
960-
v8_resource,
961-
v8_resource_name,
962-
externally_managed_resource);
925+
v8impl::AsyncContext* async_context = new v8impl::AsyncContext(
926+
reinterpret_cast<node_napi_env>(env), v8_resource, v8_resource_name);
963927

964928
*result = reinterpret_cast<napi_async_context>(async_context);
965929

0 commit comments

Comments
 (0)