Skip to content

Commit 0ec1d18

Browse files
addaleaxnodejs-github-bot
authored andcommitted
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. PR-URL: #59828 Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
1 parent ce748f6 commit 0ec1d18

File tree

4 files changed

+20
-104
lines changed

4 files changed

+20
-104
lines changed

doc/api/n-api.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5984,6 +5984,10 @@ the runtime.
59845984
<!-- YAML
59855985
added: v8.6.0
59865986
napiVersion: 1
5987+
changes:
5988+
- version: REPLACEME
5989+
pr-url: https://github.com/nodejs/node/pull/59828
5990+
description: The `async_resource` object will now be held as a strong reference.
59875991
-->
59885992

59895993
```c
@@ -6003,23 +6007,19 @@ napi_status napi_async_init(napi_env env,
60036007

60046008
Returns `napi_ok` if the API succeeded.
60056009

6006-
The `async_resource` object needs to be kept alive until
6007-
[`napi_async_destroy`][] to keep `async_hooks` related API acts correctly. In
6008-
order to retain ABI compatibility with previous versions, `napi_async_context`s
6009-
are not maintaining the strong reference to the `async_resource` objects to
6010-
avoid introducing causing memory leaks. However, if the `async_resource` is
6011-
garbage collected by JavaScript engine before the `napi_async_context` was
6012-
destroyed by `napi_async_destroy`, calling `napi_async_context` related APIs
6013-
like [`napi_open_callback_scope`][] and [`napi_make_callback`][] can cause
6014-
problems like loss of async context when using the `AsyncLocalStorage` API.
6015-
60166010
In order to retain ABI compatibility with previous versions, passing `NULL`
60176011
for `async_resource` does not result in an error. However, this is not
60186012
recommended as this will result in undesirable behavior with `async_hooks`
60196013
[`init` hooks][] and `async_hooks.executionAsyncResource()` as the resource is
60206014
now required by the underlying `async_hooks` implementation in order to provide
60216015
the linkage between async callbacks.
60226016

6017+
Previous versions of this API were not maintaining a strong reference to
6018+
`async_resource` while the `napi_async_context` object existed and instead
6019+
expected the caller to hold a strong reference. This has been changed, as a
6020+
corresponding call to [`napi_async_destroy`][] for every call to
6021+
`napi_async_init()` is a requirement in any case to avoid memory leaks.
6022+
60236023
### `napi_async_destroy`
60246024

60256025
<!-- YAML

src/node_api.cc

Lines changed: 7 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,23 +603,18 @@ 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_,
606+
: cs_(async_context->node_env(),
607+
&async_context->resource_,
637608
async_context->async_context()) {}
638609

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

644614
node_napi_env env_;
645615
double async_id_;
646616
double trigger_async_id_;
647617
v8::Global<v8::Object> resource_;
648-
bool lost_reference_;
649618
v8::Global<v8::Value> context_frame_;
650619
};
651620

@@ -943,23 +912,17 @@ napi_status NAPI_CDECL napi_async_init(napi_env env,
943912
v8::Local<v8::Context> context = env->context();
944913

945914
v8::Local<v8::Object> v8_resource;
946-
bool externally_managed_resource;
947915
if (async_resource != nullptr) {
948916
CHECK_TO_OBJECT(env, context, v8_resource, async_resource);
949-
externally_managed_resource = true;
950917
} else {
951918
v8_resource = v8::Object::New(isolate);
952-
externally_managed_resource = false;
953919
}
954920

955921
v8::Local<v8::String> v8_resource_name;
956922
CHECK_TO_STRING(env, context, v8_resource_name, async_resource_name);
957923

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

964927
*result = reinterpret_cast<napi_async_context>(async_context);
965928

test/node-api/test_async_context/test-gcable-callback.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,10 @@ setImmediate(() => {
5050
assert.strictEqual(hook_result.destroy_called, false);
5151
makeCallback(asyncResource, process, () => {
5252
const executionAsyncResource = async_hooks.executionAsyncResource();
53-
// Assuming the executionAsyncResource was created for the absence of the
54-
// initial `{ foo: 'bar' }`.
55-
// This is the worst path of `napi_async_context` related API of
56-
// recovering from the condition and not break the executionAsyncResource
57-
// shape, although the executionAsyncResource might not be correct.
53+
// Previous versions of Node-API would have gargbage-collected
54+
// the `asyncResource` object, now we can just assert that it is intact.
5855
assert.strictEqual(typeof executionAsyncResource, 'object');
59-
assert.strictEqual(executionAsyncResource.foo, undefined);
56+
assert.strictEqual(executionAsyncResource.foo, 'bar');
6057
destroyAsyncResource(asyncResource);
6158
setImmediate(() => {
6259
assert.strictEqual(hook_result.destroy_called, true);

test/node-api/test_async_context/test-gcable.js

Lines changed: 0 additions & 44 deletions
This file was deleted.

0 commit comments

Comments
 (0)