summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlegendecas <legendecas@gmail.com>2020-10-07 02:05:15 +0800
committerNode.js GitHub Bot <github-bot@iojs.org>2020-10-31 00:17:59 +0000
commit3a7537de7dd6c833cbe3f14b58e289c0a905afa5 (patch)
tree5f85a2026d2b199faf32337320359505285fab4e
parent5da2512a72f56031a11c928792392110d4001b24 (diff)
downloadnode-new-3a7537de7dd6c833cbe3f14b58e289c0a905afa5.tar.gz
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: https://github.com/nodejs/node/pull/32930 Fixes: https://github.com/nodejs/node/issues/32898 Reviewed-By: Stephen Belanger <admin@stephenbelanger.com> Reviewed-By: Michael Dawson <midawson@redhat.com> Reviewed-By: Gerhard Stöbich <deb2001-github@yahoo.de>
-rw-r--r--doc/api/n-api.md36
-rw-r--r--src/node_api.cc187
-rw-r--r--test/node-api/test_async_context/binding.c128
-rw-r--r--test/node-api/test_async_context/binding.gyp9
-rw-r--r--test/node-api/test_async_context/test-gcable-callback.js65
-rw-r--r--test/node-api/test_async_context/test-gcable.js (renamed from test/node-api/test_make_callback/test-async-hooks-gcable.js)4
-rw-r--r--test/node-api/test_async_context/test.js63
-rw-r--r--test/node-api/test_make_callback/binding.c45
-rw-r--r--test/node-api/test_make_callback/test-async-hooks.js11
-rw-r--r--test/node-api/test_make_callback/test.js15
10 files changed, 465 insertions, 98 deletions
diff --git a/doc/api/n-api.md b/doc/api/n-api.md
index 287940d1d5..9f16d2d293 100644
--- a/doc/api/n-api.md
+++ b/doc/api/n-api.md
@@ -5189,19 +5189,31 @@ napi_status napi_async_init(napi_env env,
* `[in] env`: The environment that the API is invoked under.
* `[in] async_resource`: Object associated with the async work
- that will be passed to possible `async_hooks` [`init` hooks][].
- In order to retain ABI compatibility with previous versions,
- passing `NULL` for `async_resource` does not result in an error. However,
- this results in incorrect operation of async hooks for the
- napi_async_context created. Potential issues include
- loss of async context when using the AsyncLocalStorage API.
-* `[in] async_resource_name`: Identifier for the kind of resource
- that is being provided for diagnostic information exposed by the
- `async_hooks` API.
+ that will be passed to possible `async_hooks` [`init` hooks][] and can be
+ accessed by [`async_hooks.executionAsyncResource()`][].
+* `[in] async_resource_name`: Identifier for the kind of resource that is being
+ provided for diagnostic information exposed by the `async_hooks` API.
* `[out] result`: The initialized async context.
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 `AsyncLocalStoage` 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 poor results with `async_hooks`
+[`init` hooks][] and `async_hooks.executionAsyncResource()` as the resource is
+now required by the underlying `async_hooks` implementation in order to provide
+the linkage between async callbacks.
+
### napi_async_destroy
<!-- YAML
added: v8.6.0
@@ -5288,7 +5300,9 @@ NAPI_EXTERN napi_status napi_open_callback_scope(napi_env env,
* `[in] env`: The environment that the API is invoked under.
* `[in] resource_object`: An object associated with the async work
- that will be passed to possible `async_hooks` [`init` hooks][].
+ that will be passed to possible `async_hooks` [`init` hooks][]. This
+ parameter has been deprecated and is ignored at runtime. Use the
+ `async_resource` parameter in [`napi_async_init`][] instead.
* `[in] context`: Context for the async operation that is invoking the callback.
This should be a value previously obtained from [`napi_async_init`][].
* `[out] result`: The newly created scope.
@@ -5985,6 +5999,7 @@ This API may only be called from the main thread.
[`Number.MAX_SAFE_INTEGER`]: https://tc39.github.io/ecma262/#sec-number.max_safe_integer
[`Number.MIN_SAFE_INTEGER`]: https://tc39.github.io/ecma262/#sec-number.min_safe_integer
[`Worker`]: worker_threads.md#worker_threads_class_worker
+[`async_hooks.executionAsyncResource()`]: async_hooks.md#async_hooks_async_hooks_executionasyncresource
[`global`]: globals.md#globals_global
[`init` hooks]: async_hooks.md#async_hooks_init_asyncid_type_triggerasyncid_resource
[`napi_add_async_cleanup_hook`]: #n_api_napi_add_async_cleanup_hook
@@ -5992,6 +6007,7 @@ This API may only be called from the main thread.
[`napi_add_finalizer`]: #n_api_napi_add_finalizer
[`napi_async_cleanup_hook`]: #n_api_napi_async_cleanup_hook
[`napi_async_complete_callback`]: #n_api_napi_async_complete_callback
+[`napi_async_destroy`]: #n_api_napi_async_destroy
[`napi_async_init`]: #n_api_napi_async_init
[`napi_callback`]: #n_api_napi_callback
[`napi_cancel_async_work`]: #n_api_napi_cancel_async_work
diff --git a/src/node_api.cc b/src/node_api.cc
index 12f369a809..4e932c19c2 100644
--- a/src/node_api.cc
+++ b/src/node_api.cc
@@ -1,12 +1,15 @@
+#include "async_wrap-inl.h"
#include "env-inl.h"
#define NAPI_EXPERIMENTAL
#include "js_native_api_v8.h"
+#include "memory_tracker-inl.h"
#include "node_api.h"
#include "node_binding.h"
#include "node_buffer.h"
#include "node_errors.h"
#include "node_internals.h"
#include "threadpoolwork-inl.h"
+#include "tracing/traced_value.h"
#include "util-inl.h"
#include <memory>
@@ -104,16 +107,6 @@ static inline napi_env NewEnv(v8::Local<v8::Context> context) {
return result;
}
-static inline napi_callback_scope
-JsCallbackScopeFromV8CallbackScope(node::CallbackScope* s) {
- return reinterpret_cast<napi_callback_scope>(s);
-}
-
-static inline node::CallbackScope*
-V8CallbackScopeFromJsCallbackScope(napi_callback_scope s) {
- return reinterpret_cast<node::CallbackScope*>(s);
-}
-
static inline void trigger_fatal_exception(
napi_env env, v8::Local<v8::Value> local_err) {
v8::Local<v8::Message> local_msg =
@@ -435,6 +428,111 @@ class ThreadSafeFunction : public node::AsyncResource {
bool handles_closing;
};
+/**
+ * Compared to node::AsyncResource, the resource object in AsyncContext is
+ * gc-able. AsyncContext holds a weak reference to the resource object.
+ * AsyncContext::MakeCallback doesn't implicitly set the receiver of the
+ * callback to the resource object.
+ */
+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)
+ : env_(env) {
+ async_id_ = node_env()->new_async_id();
+ trigger_async_id_ = node_env()->get_default_trigger_async_id();
+ resource_.Reset(node_env()->isolate(), resource_object);
+ lost_reference_ = false;
+ if (externally_managed_resource) {
+ resource_.SetWeak(
+ this, AsyncContext::WeakCallback, v8::WeakCallbackType::kParameter);
+ }
+
+ node::AsyncWrap::EmitAsyncInit(node_env(),
+ resource_object,
+ resource_name,
+ async_id_,
+ trigger_async_id_);
+ }
+
+ ~AsyncContext() {
+ resource_.Reset();
+ lost_reference_ = true;
+ 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(),
+ recv,
+ callback,
+ argc,
+ argv,
+ {async_id_, trigger_async_id_});
+ }
+
+ inline napi_callback_scope OpenCallbackScope() {
+ EnsureReference();
+ napi_callback_scope it =
+ reinterpret_cast<napi_callback_scope>(new CallbackScope(this));
+ env_->open_callback_scopes++;
+ return it;
+ }
+
+ 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());
+ }
+ inline node::async_context async_context() {
+ return {async_id_, trigger_async_id_};
+ }
+
+ static inline void CloseCallbackScope(node_napi_env env,
+ napi_callback_scope s) {
+ CallbackScope* callback_scope = reinterpret_cast<CallbackScope*>(s);
+ delete callback_scope;
+ 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 CallbackScope : public node::CallbackScope {
+ public:
+ explicit CallbackScope(AsyncContext* async_context)
+ : node::CallbackScope(async_context->node_env()->isolate(),
+ async_context->resource_.Get(
+ async_context->node_env()->isolate()),
+ async_context->async_context()) {}
+ };
+
+ node_napi_env env_;
+ double async_id_;
+ double trigger_async_id_;
+ v8::Global<v8::Object> resource_;
+ bool lost_reference_;
+};
+
} // end of anonymous namespace
} // end of namespace v8impl
@@ -627,7 +725,7 @@ NAPI_NO_RETURN void napi_fatal_error(const char* location,
}
napi_status napi_open_callback_scope(napi_env env,
- napi_value resource_object,
+ napi_value /** ignored */,
napi_async_context async_context_handle,
napi_callback_scope* result) {
// Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw
@@ -635,20 +733,11 @@ napi_status napi_open_callback_scope(napi_env env,
CHECK_ENV(env);
CHECK_ARG(env, result);
- v8::Local<v8::Context> context = env->context();
-
- node::async_context* node_async_context =
- reinterpret_cast<node::async_context*>(async_context_handle);
-
- v8::Local<v8::Object> resource;
- CHECK_TO_OBJECT(env, context, resource, resource_object);
+ v8impl::AsyncContext* node_async_context =
+ reinterpret_cast<v8impl::AsyncContext*>(async_context_handle);
- *result = v8impl::JsCallbackScopeFromV8CallbackScope(
- new node::CallbackScope(env->isolate,
- resource,
- *node_async_context));
+ *result = node_async_context->OpenCallbackScope();
- env->open_callback_scopes++;
return napi_clear_last_error(env);
}
@@ -661,8 +750,9 @@ napi_status napi_close_callback_scope(napi_env env, napi_callback_scope scope) {
return napi_callback_scope_mismatch;
}
- env->open_callback_scopes--;
- delete v8impl::V8CallbackScopeFromJsCallbackScope(scope);
+ v8impl::AsyncContext::CloseCallbackScope(reinterpret_cast<node_napi_env>(env),
+ scope);
+
return napi_clear_last_error(env);
}
@@ -678,20 +768,24 @@ napi_status 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);
- // TODO(jasongin): Consider avoiding allocation here by using
- // a tagged pointer with 2×31 bit fields instead.
- node::async_context* async_context = new node::async_context();
+ auto async_context =
+ new v8impl::AsyncContext(reinterpret_cast<node_napi_env>(env),
+ v8_resource,
+ v8_resource_name,
+ externally_managed_resource);
- *async_context = node::EmitAsyncInit(isolate, v8_resource, v8_resource_name);
*result = reinterpret_cast<napi_async_context>(async_context);
return napi_clear_last_error(env);
@@ -702,11 +796,8 @@ napi_status napi_async_destroy(napi_env env,
CHECK_ENV(env);
CHECK_ARG(env, async_context);
- node::async_context* node_async_context =
- reinterpret_cast<node::async_context*>(async_context);
- node::EmitAsyncDestroy(
- reinterpret_cast<node_napi_env>(env)->node_env(),
- *node_async_context);
+ v8impl::AsyncContext* node_async_context =
+ reinterpret_cast<v8impl::AsyncContext*>(async_context);
delete node_async_context;
@@ -734,17 +825,25 @@ napi_status napi_make_callback(napi_env env,
v8::Local<v8::Function> v8func;
CHECK_TO_FUNCTION(env, v8func, func);
- node::async_context* node_async_context =
- reinterpret_cast<node::async_context*>(async_context);
- if (node_async_context == nullptr) {
- static node::async_context empty_context = { 0, 0 };
- node_async_context = &empty_context;
- }
+ v8::MaybeLocal<v8::Value> callback_result;
- v8::MaybeLocal<v8::Value> callback_result = node::MakeCallback(
- env->isolate, v8recv, v8func, argc,
- reinterpret_cast<v8::Local<v8::Value>*>(const_cast<napi_value*>(argv)),
- *node_async_context);
+ if (async_context == nullptr) {
+ callback_result = node::MakeCallback(
+ env->isolate,
+ v8recv,
+ v8func,
+ argc,
+ reinterpret_cast<v8::Local<v8::Value>*>(const_cast<napi_value*>(argv)),
+ {0, 0});
+ } else {
+ auto node_async_context =
+ reinterpret_cast<v8impl::AsyncContext*>(async_context);
+ callback_result = node_async_context->MakeCallback(
+ v8recv,
+ v8func,
+ argc,
+ reinterpret_cast<v8::Local<v8::Value>*>(const_cast<napi_value*>(argv)));
+ }
if (try_catch.HasCaught()) {
return napi_set_last_error(env, napi_pending_exception);
diff --git a/test/node-api/test_async_context/binding.c b/test/node-api/test_async_context/binding.c
new file mode 100644
index 0000000000..749bb05d25
--- /dev/null
+++ b/test/node-api/test_async_context/binding.c
@@ -0,0 +1,128 @@
+#include <node_api.h>
+#include <assert.h>
+#include "../../js-native-api/common.h"
+
+#define MAX_ARGUMENTS 10
+#define RESERVED_ARGS 3
+
+static napi_value MakeCallback(napi_env env, napi_callback_info info) {
+ size_t argc = MAX_ARGUMENTS;
+ size_t n;
+ napi_value args[MAX_ARGUMENTS];
+ // NOLINTNEXTLINE (readability/null_usage)
+ NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
+
+ NAPI_ASSERT(env, argc > 0, "Wrong number of arguments");
+
+ napi_value async_context_wrap = args[0];
+ napi_value recv = args[1];
+ napi_value func = args[2];
+
+ napi_value argv[MAX_ARGUMENTS - RESERVED_ARGS];
+ for (n = RESERVED_ARGS; n < argc; n += 1) {
+ argv[n - RESERVED_ARGS] = args[n];
+ }
+
+ napi_valuetype func_type;
+ NAPI_CALL(env, napi_typeof(env, func, &func_type));
+
+ napi_async_context context;
+ NAPI_CALL(env, napi_unwrap(env, async_context_wrap, &context));
+
+ napi_value result;
+ if (func_type == napi_function) {
+ NAPI_CALL(env, napi_make_callback(
+ env, context, recv, func, argc - RESERVED_ARGS, argv, &result));
+ } else {
+ NAPI_ASSERT(env, false, "Unexpected argument type");
+ }
+
+ return result;
+}
+
+static void AsyncDestroyCb(napi_env env, void* data, void* hint) {
+ napi_status status = napi_async_destroy(env, (napi_async_context)data);
+ // We cannot use NAPI_ASSERT_RETURN_VOID because we need to have a JS stack
+ // below in order to use exceptions.
+ assert(status == napi_ok);
+}
+
+#define CREATE_ASYNC_RESOURCE_ARGC 2
+
+static napi_value CreateAsyncResource(napi_env env, napi_callback_info info) {
+ napi_value async_context_wrap;
+ NAPI_CALL(env, napi_create_object(env, &async_context_wrap));
+
+ size_t argc = CREATE_ASYNC_RESOURCE_ARGC;
+ napi_value args[CREATE_ASYNC_RESOURCE_ARGC];
+ // NOLINTNEXTLINE (readability/null_usage)
+ NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
+
+ napi_value resource = args[0];
+ napi_value js_destroy_on_finalizer = args[1];
+ napi_valuetype resource_type;
+ NAPI_CALL(env, napi_typeof(env, resource, &resource_type));
+ if (resource_type != napi_object) {
+ resource = NULL;
+ }
+
+ napi_value resource_name;
+ NAPI_CALL(env, napi_create_string_utf8(
+ env, "test_async", NAPI_AUTO_LENGTH, &resource_name));
+
+ napi_async_context context;
+ NAPI_CALL(env, napi_async_init(env, resource, resource_name, &context));
+
+ bool destroy_on_finalizer = true;
+ if (argc == 2) {
+ NAPI_CALL(env, napi_get_value_bool(env, js_destroy_on_finalizer, &destroy_on_finalizer));
+ }
+ if (resource_type == napi_object && destroy_on_finalizer) {
+ NAPI_CALL(env, napi_add_finalizer(
+ env, resource, (void*)context, AsyncDestroyCb, NULL, NULL));
+ }
+ NAPI_CALL(env, napi_wrap(env, async_context_wrap, context, NULL, NULL, NULL));
+ return async_context_wrap;
+}
+
+#define DESTROY_ASYNC_RESOURCE_ARGC 1
+
+static napi_value DestroyAsyncResource(napi_env env, napi_callback_info info) {
+ size_t argc = DESTROY_ASYNC_RESOURCE_ARGC;
+ napi_value args[DESTROY_ASYNC_RESOURCE_ARGC];
+ // NOLINTNEXTLINE (readability/null_usage)
+ NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
+ NAPI_ASSERT(env, argc == 1, "Wrong number of arguments");
+
+ napi_value async_context_wrap = args[0];
+
+ napi_async_context async_context;
+ NAPI_CALL(env, napi_remove_wrap(env, async_context_wrap, &async_context));
+ NAPI_CALL(env, napi_async_destroy(env, async_context));
+
+ return async_context_wrap;
+}
+
+static
+napi_value Init(napi_env env, napi_value exports) {
+ napi_value fn;
+ NAPI_CALL(env, napi_create_function(
+ // NOLINTNEXTLINE (readability/null_usage)
+ env, NULL, NAPI_AUTO_LENGTH, MakeCallback, NULL, &fn));
+ NAPI_CALL(env, napi_set_named_property(env, exports, "makeCallback", fn));
+ NAPI_CALL(env, napi_create_function(
+ // NOLINTNEXTLINE (readability/null_usage)
+ env, NULL, NAPI_AUTO_LENGTH, CreateAsyncResource, NULL, &fn));
+ NAPI_CALL(env, napi_set_named_property(
+ env, exports, "createAsyncResource", fn));
+
+ NAPI_CALL(env, napi_create_function(
+ // NOLINTNEXTLINE (readability/null_usage)
+ env, NULL, NAPI_AUTO_LENGTH, DestroyAsyncResource, NULL, &fn));
+ NAPI_CALL(env, napi_set_named_property(
+ env, exports, "destroyAsyncResource", fn));
+
+ return exports;
+}
+
+NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
diff --git a/test/node-api/test_async_context/binding.gyp b/test/node-api/test_async_context/binding.gyp
new file mode 100644
index 0000000000..23daf50791
--- /dev/null
+++ b/test/node-api/test_async_context/binding.gyp
@@ -0,0 +1,9 @@
+{
+ 'targets': [
+ {
+ 'target_name': 'binding',
+ 'defines': [ 'V8_DEPRECATION_WARNINGS=1' ],
+ 'sources': [ 'binding.c' ]
+ }
+ ]
+}
diff --git a/test/node-api/test_async_context/test-gcable-callback.js b/test/node-api/test_async_context/test-gcable-callback.js
new file mode 100644
index 0000000000..e66080bb6a
--- /dev/null
+++ b/test/node-api/test_async_context/test-gcable-callback.js
@@ -0,0 +1,65 @@
+'use strict';
+// Flags: --gc-interval=100 --gc-global
+
+const common = require('../../common');
+const assert = require('assert');
+const async_hooks = require('async_hooks');
+const {
+ createAsyncResource,
+ destroyAsyncResource,
+ makeCallback,
+} = 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();
+const asyncResource = createAsyncResource(
+ { foo: 'bar' },
+ /* destroy_on_finalizer */false
+);
+
+// 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, 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.
+ assert.strictEqual(typeof executionAsyncResource, 'object');
+ assert.strictEqual(executionAsyncResource.foo, undefined);
+ destroyAsyncResource(asyncResource);
+ setImmediate(() => {
+ assert.strictEqual(hook_result.destroy_called, true);
+ });
+ });
+});
diff --git a/test/node-api/test_make_callback/test-async-hooks-gcable.js b/test/node-api/test_async_context/test-gcable.js
index a9b4d3d75d..288b741226 100644
--- a/test/node-api/test_make_callback/test-async-hooks-gcable.js
+++ b/test/node-api/test_async_context/test-gcable.js
@@ -17,7 +17,7 @@ const hook_result = {
const test_hook = async_hooks.createHook({
init: (id, type) => {
- if (type === 'test_gcable') {
+ if (type === 'test_async') {
hook_result.id = id;
hook_result.init_called = true;
}
@@ -28,7 +28,7 @@ const test_hook = async_hooks.createHook({
});
test_hook.enable();
-createAsyncResource();
+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
diff --git a/test/node-api/test_async_context/test.js b/test/node-api/test_async_context/test.js
new file mode 100644
index 0000000000..2cf00b1ef7
--- /dev/null
+++ b/test/node-api/test_async_context/test.js
@@ -0,0 +1,63 @@
+'use strict';
+// Flags: --gc-interval=100 --gc-global
+
+const common = require('../../common');
+const assert = require('assert');
+const async_hooks = require('async_hooks');
+const {
+ makeCallback,
+ createAsyncResource,
+ destroyAsyncResource,
+} = require(`./build/${common.buildType}/binding`);
+
+const hook_result = {
+ id: null,
+ resource: null,
+ init_called: false,
+ destroy_called: false,
+};
+
+const test_hook = async_hooks.createHook({
+ init: (id, type, triggerAsyncId, resource) => {
+ if (type === 'test_async') {
+ hook_result.id = id;
+ hook_result.init_called = true;
+ hook_result.resource = resource;
+ }
+ },
+ destroy: (id) => {
+ if (id === hook_result.id) hook_result.destroy_called = true;
+ },
+});
+
+test_hook.enable();
+const resourceWrap = createAsyncResource(
+ /**
+ * set resource to NULL to generate a managed resource object
+ */
+ undefined
+);
+
+assert.strictEqual(hook_result.destroy_called, false);
+const recv = {};
+makeCallback(resourceWrap, recv, function callback() {
+ assert.strictEqual(hook_result.destroy_called, false);
+ assert.strictEqual(
+ hook_result.resource,
+ async_hooks.executionAsyncResource()
+ );
+ assert.strictEqual(this, recv);
+
+ setImmediate(() => {
+ assert.strictEqual(hook_result.destroy_called, false);
+ assert.notStrictEqual(
+ hook_result.resource,
+ async_hooks.executionAsyncResource()
+ );
+
+ destroyAsyncResource(resourceWrap);
+ setImmediate(() => {
+ assert.strictEqual(hook_result.destroy_called, true);
+ });
+ });
+});
diff --git a/test/node-api/test_make_callback/binding.c b/test/node-api/test_make_callback/binding.c
index 782cf0f6fb..214e0a4e18 100644
--- a/test/node-api/test_make_callback/binding.c
+++ b/test/node-api/test_make_callback/binding.c
@@ -3,6 +3,7 @@
#include "../../js-native-api/common.h"
#define MAX_ARGUMENTS 10
+#define RESERVED_ARGS 3
static napi_value MakeCallback(napi_env env, napi_callback_info info) {
size_t argc = MAX_ARGUMENTS;
@@ -13,12 +14,13 @@ static napi_value MakeCallback(napi_env env, napi_callback_info info) {
NAPI_ASSERT(env, argc > 0, "Wrong number of arguments");
- napi_value recv = args[0];
- napi_value func = args[1];
+ napi_value resource = args[0];
+ napi_value recv = args[1];
+ napi_value func = args[2];
- napi_value argv[MAX_ARGUMENTS - 2];
- for (n = 2; n < argc; n += 1) {
- argv[n - 2] = args[n];
+ napi_value argv[MAX_ARGUMENTS - RESERVED_ARGS];
+ for (n = RESERVED_ARGS; n < argc; n += 1) {
+ argv[n - RESERVED_ARGS] = args[n];
}
napi_valuetype func_type;
@@ -30,12 +32,12 @@ static napi_value MakeCallback(napi_env env, napi_callback_info info) {
env, "test", NAPI_AUTO_LENGTH, &resource_name));
napi_async_context context;
- NAPI_CALL(env, napi_async_init(env, func, resource_name, &context));
+ NAPI_CALL(env, napi_async_init(env, resource, resource_name, &context));
napi_value result;
if (func_type == napi_function) {
NAPI_CALL(env, napi_make_callback(
- env, context, recv, func, argc - 2, argv, &result));
+ env, context, recv, func, argc - RESERVED_ARGS, argv, &result));
} else {
NAPI_ASSERT(env, false, "Unexpected argument type");
}
@@ -45,30 +47,6 @@ static napi_value MakeCallback(napi_env env, napi_callback_info info) {
return result;
}
-static void AsyncDestroyCb(napi_env env, void* data, void* hint) {
- napi_status status = napi_async_destroy(env, (napi_async_context)data);
- // We cannot use NAPI_ASSERT_RETURN_VOID because we need to have a JS stack
- // below in order to use exceptions.
- assert(status == napi_ok);
-}
-
-static napi_value CreateAsyncResource(napi_env env, napi_callback_info info) {
- napi_value object;
- NAPI_CALL(env, napi_create_object(env, &object));
-
- napi_value resource_name;
- NAPI_CALL(env, napi_create_string_utf8(
- env, "test_gcable", NAPI_AUTO_LENGTH, &resource_name));
-
- napi_async_context context;
- NAPI_CALL(env, napi_async_init(env, object, resource_name, &context));
-
- NAPI_CALL(env, napi_add_finalizer(
- env, object, (void*)context, AsyncDestroyCb, NULL, NULL));
-
- return object;
-}
-
static
napi_value Init(napi_env env, napi_value exports) {
napi_value fn;
@@ -76,11 +54,6 @@ napi_value Init(napi_env env, napi_value exports) {
// NOLINTNEXTLINE (readability/null_usage)
env, NULL, NAPI_AUTO_LENGTH, MakeCallback, NULL, &fn));
NAPI_CALL(env, napi_set_named_property(env, exports, "makeCallback", fn));
- NAPI_CALL(env, napi_create_function(
- // NOLINTNEXTLINE (readability/null_usage)
- env, NULL, NAPI_AUTO_LENGTH, CreateAsyncResource, NULL, &fn));
- NAPI_CALL(env, napi_set_named_property(
- env, exports, "createAsyncResource", fn));
return exports;
}
diff --git a/test/node-api/test_make_callback/test-async-hooks.js b/test/node-api/test_make_callback/test-async-hooks.js
index 755a2389c6..ca31ac4995 100644
--- a/test/node-api/test_make_callback/test-async-hooks.js
+++ b/test/node-api/test_make_callback/test-async-hooks.js
@@ -33,7 +33,16 @@ const test_hook = async_hooks.createHook({
});
test_hook.enable();
-makeCallback(process, function() {});
+
+/**
+ * Resource should be able to be arbitrary objects without special internal
+ * slots. Testing with plain object here.
+ */
+const resource = {};
+makeCallback(resource, process, function cb() {
+ assert.strictEqual(this, process);
+ assert.strictEqual(async_hooks.executionAsyncResource(), resource);
+});
assert.strictEqual(hook_result.init_called, true);
assert.strictEqual(hook_result.before_called, true);
diff --git a/test/node-api/test_make_callback/test.js b/test/node-api/test_make_callback/test.js
index dba550a492..d0b4b22500 100644
--- a/test/node-api/test_make_callback/test.js
+++ b/test/node-api/test_make_callback/test.js
@@ -13,20 +13,25 @@ function myMultiArgFunc(arg1, arg2, arg3) {
return 42;
}
-assert.strictEqual(makeCallback(process, common.mustCall(function() {
+/**
+ * Resource should be able to be arbitrary objects without special internal
+ * slots. Testing with plain object here.
+ */
+const resource = {};
+assert.strictEqual(makeCallback(resource, process, common.mustCall(function() {
assert.strictEqual(arguments.length, 0);
assert.strictEqual(this, process);
return 42;
})), 42);
-assert.strictEqual(makeCallback(process, common.mustCall(function(x) {
+assert.strictEqual(makeCallback(resource, process, common.mustCall(function(x) {
assert.strictEqual(arguments.length, 1);
assert.strictEqual(this, process);
assert.strictEqual(x, 1337);
return 42;
}), 1337), 42);
-assert.strictEqual(makeCallback(this,
+assert.strictEqual(makeCallback(resource, this,
common.mustCall(myMultiArgFunc), 1, 2, 3), 42);
// TODO(node-api): napi_make_callback needs to support
@@ -62,7 +67,7 @@ const target = vm.runInNewContext(`
return Object;
})
`);
-assert.notStrictEqual(makeCallback(process, target, Object), Object);
+assert.notStrictEqual(makeCallback(resource, process, target, Object), Object);
// Runs in inner context.
const forward = vm.runInNewContext(`
@@ -78,4 +83,4 @@ function endpoint($Object) {
return Object;
}
-assert.strictEqual(makeCallback(process, forward, endpoint), Object);
+assert.strictEqual(makeCallback(resource, process, forward, endpoint), Object);