mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 12:20:27 +01:00
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: https://github.com/nodejs/node/pull/59828 Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
This commit is contained in:
parent
ce748f6cae
commit
0ec1d186f4
|
|
@ -5984,6 +5984,10 @@ the runtime.
|
|||
<!-- YAML
|
||||
added: v8.6.0
|
||||
napiVersion: 1
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/59828
|
||||
description: The `async_resource` object will now be held as a strong reference.
|
||||
-->
|
||||
|
||||
```c
|
||||
|
|
@ -6003,16 +6007,6 @@ napi_status napi_async_init(napi_env env,
|
|||
|
||||
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 `AsyncLocalStorage` 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 in undesirable behavior with `async_hooks`
|
||||
|
|
@ -6020,6 +6014,12 @@ recommended as this will result in undesirable behavior with `async_hooks`
|
|||
now required by the underlying `async_hooks` implementation in order to provide
|
||||
the linkage between async callbacks.
|
||||
|
||||
Previous versions of this API were not maintaining a strong reference to
|
||||
`async_resource` while the `napi_async_context` object existed and instead
|
||||
expected the caller to hold a strong reference. This has been changed, as a
|
||||
corresponding call to [`napi_async_destroy`][] for every call to
|
||||
`napi_async_init()` is a requirement in any case to avoid memory leaks.
|
||||
|
||||
### `napi_async_destroy`
|
||||
|
||||
<!-- YAML
|
||||
|
|
|
|||
|
|
@ -538,19 +538,13 @@ 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)
|
||||
v8::Local<v8::String> resource_name)
|
||||
: env_(env) {
|
||||
async_id_ = node_env()->new_async_id();
|
||||
trigger_async_id_ = node_env()->get_default_trigger_async_id();
|
||||
v8::Isolate* isolate = node_env()->isolate();
|
||||
resource_.Reset(isolate, resource_object);
|
||||
context_frame_.Reset(isolate, node::async_context_frame::current(isolate));
|
||||
lost_reference_ = false;
|
||||
if (externally_managed_resource) {
|
||||
resource_.SetWeak(
|
||||
this, AsyncContext::WeakCallback, v8::WeakCallbackType::kParameter);
|
||||
}
|
||||
|
||||
node::AsyncWrap::EmitAsyncInit(node_env(),
|
||||
resource_object,
|
||||
|
|
@ -559,18 +553,13 @@ class AsyncContext {
|
|||
trigger_async_id_);
|
||||
}
|
||||
|
||||
~AsyncContext() {
|
||||
resource_.Reset();
|
||||
lost_reference_ = true;
|
||||
node::AsyncWrap::EmitDestroy(node_env(), async_id_);
|
||||
}
|
||||
~AsyncContext() { 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(),
|
||||
|
|
@ -583,21 +572,11 @@ class AsyncContext {
|
|||
}
|
||||
|
||||
inline napi_callback_scope OpenCallbackScope() {
|
||||
EnsureReference();
|
||||
auto scope = new HeapAllocatedCallbackScope(this);
|
||||
env_->open_callback_scopes++;
|
||||
return scope->to_opaque();
|
||||
}
|
||||
|
||||
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());
|
||||
|
|
@ -609,15 +588,10 @@ class AsyncContext {
|
|||
static inline void CloseCallbackScope(node_napi_env env,
|
||||
napi_callback_scope s) {
|
||||
delete HeapAllocatedCallbackScope::FromOpaque(s);
|
||||
CHECK_GT(env->open_callback_scopes, 0);
|
||||
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 HeapAllocatedCallbackScope final {
|
||||
public:
|
||||
|
|
@ -629,15 +603,11 @@ class AsyncContext {
|
|||
}
|
||||
|
||||
explicit HeapAllocatedCallbackScope(AsyncContext* async_context)
|
||||
: resource_storage_(async_context->node_env()->isolate(),
|
||||
async_context->resource_.Get(
|
||||
async_context->node_env()->isolate())),
|
||||
cs_(async_context->node_env(),
|
||||
&resource_storage_,
|
||||
: cs_(async_context->node_env(),
|
||||
&async_context->resource_,
|
||||
async_context->async_context()) {}
|
||||
|
||||
private:
|
||||
v8::Global<v8::Object> resource_storage_;
|
||||
node::CallbackScope cs_;
|
||||
};
|
||||
|
||||
|
|
@ -645,7 +615,6 @@ class AsyncContext {
|
|||
double async_id_;
|
||||
double trigger_async_id_;
|
||||
v8::Global<v8::Object> resource_;
|
||||
bool lost_reference_;
|
||||
v8::Global<v8::Value> context_frame_;
|
||||
};
|
||||
|
||||
|
|
@ -943,23 +912,17 @@ napi_status NAPI_CDECL 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);
|
||||
|
||||
v8impl::AsyncContext* async_context =
|
||||
new v8impl::AsyncContext(reinterpret_cast<node_napi_env>(env),
|
||||
v8_resource,
|
||||
v8_resource_name,
|
||||
externally_managed_resource);
|
||||
v8impl::AsyncContext* async_context = new v8impl::AsyncContext(
|
||||
reinterpret_cast<node_napi_env>(env), v8_resource, v8_resource_name);
|
||||
|
||||
*result = reinterpret_cast<napi_async_context>(async_context);
|
||||
|
||||
|
|
|
|||
|
|
@ -50,13 +50,10 @@ setImmediate(() => {
|
|||
assert.strictEqual(hook_result.destroy_called, false);
|
||||
makeCallback(asyncResource, process, () => {
|
||||
const executionAsyncResource = async_hooks.executionAsyncResource();
|
||||
// Assuming the executionAsyncResource was created for the absence of the
|
||||
// initial `{ foo: 'bar' }`.
|
||||
// This is the worst path of `napi_async_context` related API of
|
||||
// recovering from the condition and not break the executionAsyncResource
|
||||
// shape, although the executionAsyncResource might not be correct.
|
||||
// Previous versions of Node-API would have gargbage-collected
|
||||
// the `asyncResource` object, now we can just assert that it is intact.
|
||||
assert.strictEqual(typeof executionAsyncResource, 'object');
|
||||
assert.strictEqual(executionAsyncResource.foo, undefined);
|
||||
assert.strictEqual(executionAsyncResource.foo, 'bar');
|
||||
destroyAsyncResource(asyncResource);
|
||||
setImmediate(() => {
|
||||
assert.strictEqual(hook_result.destroy_called, true);
|
||||
|
|
|
|||
|
|
@ -1,44 +0,0 @@
|
|||
'use strict';
|
||||
// Flags: --gc-interval=100 --gc-global
|
||||
|
||||
const common = require('../../common');
|
||||
const assert = require('assert');
|
||||
const async_hooks = require('async_hooks');
|
||||
const { createAsyncResource } = require(`./build/${common.buildType}/binding`);
|
||||
|
||||
// Test for https://github.com/nodejs/node/issues/27218:
|
||||
// napi_async_destroy() can be called during a regular garbage collection run.
|
||||
|
||||
const hook_result = {
|
||||
id: null,
|
||||
init_called: false,
|
||||
destroy_called: false,
|
||||
};
|
||||
|
||||
const test_hook = async_hooks.createHook({
|
||||
init: (id, type) => {
|
||||
if (type === 'test_async') {
|
||||
hook_result.id = id;
|
||||
hook_result.init_called = true;
|
||||
}
|
||||
},
|
||||
destroy: (id) => {
|
||||
if (id === hook_result.id) hook_result.destroy_called = true;
|
||||
},
|
||||
});
|
||||
|
||||
test_hook.enable();
|
||||
createAsyncResource({});
|
||||
|
||||
// Trigger GC. This does *not* use global.gc(), because what we want to verify
|
||||
// is that `napi_async_destroy()` can be called when there is no JS context
|
||||
// on the stack at the time of GC.
|
||||
// Currently, using --gc-interval=100 + 1M elements seems to work fine for this.
|
||||
const arr = new Array(1024 * 1024);
|
||||
for (let i = 0; i < arr.length; i++)
|
||||
arr[i] = {};
|
||||
|
||||
assert.strictEqual(hook_result.destroy_called, false);
|
||||
setImmediate(() => {
|
||||
assert.strictEqual(hook_result.destroy_called, true);
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user