Skip to content

Commit e34a4b8

Browse files
committed
fixup! fixup! src: use Global for storing resource in Node-API callback scope
1 parent db0be9c commit e34a4b8

File tree

2 files changed

+54
-18
lines changed

2 files changed

+54
-18
lines changed

doc/api/n-api.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5924,10 +5924,6 @@ 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.
59315927
-->
59325928

59335929
```c
@@ -5947,19 +5943,23 @@ napi_status napi_async_init(napi_env env,
59475943

59485944
Returns `napi_ok` if the API succeeded.
59495945

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+
59505956
In order to retain ABI compatibility with previous versions, passing `NULL`
59515957
for `async_resource` does not result in an error. However, this is not
59525958
recommended as this will result in undesirable behavior with `async_hooks`
59535959
[`init` hooks][] and `async_hooks.executionAsyncResource()` as the resource is
59545960
now required by the underlying `async_hooks` implementation in order to provide
59555961
the linkage between async callbacks.
59565962

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: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -538,13 +538,19 @@ class AsyncContext {
538538
public:
539539
AsyncContext(node_napi_env env,
540540
v8::Local<v8::Object> resource_object,
541-
v8::Local<v8::String> resource_name)
541+
const v8::Local<v8::String> resource_name,
542+
bool externally_managed_resource)
542543
: env_(env) {
543544
async_id_ = node_env()->new_async_id();
544545
trigger_async_id_ = node_env()->get_default_trigger_async_id();
545546
v8::Isolate* isolate = node_env()->isolate();
546547
resource_.Reset(isolate, resource_object);
547548
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+
}
548554

549555
node::AsyncWrap::EmitAsyncInit(node_env(),
550556
resource_object,
@@ -553,13 +559,18 @@ class AsyncContext {
553559
trigger_async_id_);
554560
}
555561

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

558568
inline v8::MaybeLocal<v8::Value> MakeCallback(
559569
v8::Local<v8::Object> recv,
560570
const v8::Local<v8::Function> callback,
561571
int argc,
562572
v8::Local<v8::Value> argv[]) {
573+
EnsureReference();
563574
return node::InternalMakeCallback(
564575
node_env(),
565576
resource(),
@@ -572,11 +583,21 @@ class AsyncContext {
572583
}
573584

574585
inline napi_callback_scope OpenCallbackScope() {
586+
EnsureReference();
575587
auto scope = new HeapAllocatedCallbackScope(this);
576588
env_->open_callback_scopes++;
577589
return scope->to_opaque();
578590
}
579591

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+
580601
inline node::Environment* node_env() { return env_->node_env(); }
581602
inline v8::Local<v8::Object> resource() {
582603
return resource_.Get(node_env()->isolate());
@@ -588,10 +609,15 @@ class AsyncContext {
588609
static inline void CloseCallbackScope(node_napi_env env,
589610
napi_callback_scope s) {
590611
delete HeapAllocatedCallbackScope::FromOpaque(s);
591-
CHECK_GT(env->open_callback_scopes, 0);
592612
env->open_callback_scopes--;
593613
}
594614

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+
595621
private:
596622
class HeapAllocatedCallbackScope final {
597623
public:
@@ -603,11 +629,15 @@ class AsyncContext {
603629
}
604630

605631
explicit HeapAllocatedCallbackScope(AsyncContext* async_context)
606-
cs_(async_context->node_env(),
607-
&async_context->resource_storage_,
608-
async_context->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()) {}
609638

610639
private:
640+
v8::Global<v8::Object> resource_storage_;
611641
node::CallbackScope cs_;
612642
};
613643

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

915945
v8::Local<v8::Object> v8_resource;
946+
bool externally_managed_resource;
916947
if (async_resource != nullptr) {
917948
CHECK_TO_OBJECT(env, context, v8_resource, async_resource);
949+
externally_managed_resource = true;
918950
} else {
919951
v8_resource = v8::Object::New(isolate);
952+
externally_managed_resource = false;
920953
}
921954

922955
v8::Local<v8::String> v8_resource_name;
923956
CHECK_TO_STRING(env, context, v8_resource_name, async_resource_name);
924957

925-
v8impl::AsyncContext* async_context = new v8impl::AsyncContext(
926-
reinterpret_cast<node_napi_env>(env), v8_resource, v8_resource_name);
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);
927963

928964
*result = reinterpret_cast<napi_async_context>(async_context);
929965

0 commit comments

Comments
 (0)