summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/api/n-api.md78
-rw-r--r--src/env.h1
-rw-r--r--src/js_native_api.h9
-rw-r--r--src/js_native_api_v8.cc33
-rw-r--r--src/js_native_api_v8.h57
-rw-r--r--src/node_api.cc74
-rw-r--r--test/common/require-as.js28
-rw-r--r--test/js-native-api/test_instance_data/binding.gyp11
-rw-r--r--test/js-native-api/test_instance_data/test.js40
-rw-r--r--test/js-native-api/test_instance_data/test_instance_data.c97
-rw-r--r--test/node-api/test_env_sharing/binding.gyp12
-rw-r--r--test/node-api/test_env_sharing/compare_env.c23
-rw-r--r--test/node-api/test_env_sharing/store_env.c10
-rw-r--r--test/node-api/test_env_sharing/test.js9
-rw-r--r--test/node-api/test_instance_data/binding.gyp10
-rw-r--r--test/node-api/test_instance_data/test.js35
-rw-r--r--test/node-api/test_instance_data/test_instance_data.c236
17 files changed, 630 insertions, 133 deletions
diff --git a/doc/api/n-api.md b/doc/api/n-api.md
index e4b9e4c896..841e756e82 100644
--- a/doc/api/n-api.md
+++ b/doc/api/n-api.md
@@ -251,6 +251,82 @@ NAPI_MODULE_INIT() {
}
```
+## Environment Life Cycle APIs
+
+> Stability: 1 - Experimental
+
+[Section 8.7][] of the [ECMAScript Language Specification][] defines the concept
+of an "Agent" as a self-contained environment in which JavaScript code runs.
+Multiple such Agents may be started and terminated either concurrently or in
+sequence by the process.
+
+A Node.js environment corresponds to an ECMAScript Agent. In the main process,
+an environment is created at startup, and additional environments can be created
+on separate threads to serve as [worker threads][]. When Node.js is embedded in
+another application, the main thread of the application may also construct and
+destroy a Node.js environment multiple times during the life cycle of the
+application process such that each Node.js environment created by the
+application may, in turn, during its life cycle create and destroy additional
+environments as worker threads.
+
+From the perspective of a native addon this means that the bindings it provides
+may be called multiple times, from multiple contexts, and even concurrently from
+multiple threads.
+
+Native addons may need to allocate global state of which they make use during
+their entire life cycle such that the state must be unique to each instance of
+the addon.
+
+To this env, N-API provides a way to allocate data such that its life cycle is
+tied to the life cycle of the Agent.
+
+### napi_set_instance_data
+<!-- YAML
+added: REPLACEME
+-->
+
+```C
+napi_status napi_set_instance_data(napi_env env,
+ void* data,
+ napi_finalize finalize_cb,
+ void* finalize_hint);
+```
+
+- `[in] env`: The environment that the N-API call is invoked under.
+- `[in] data`: The data item to make available to bindings of this instance.
+- `[in] finalize_cb`: The function to call when the environment is being torn
+down. The function receives `data` so that it might free it.
+- `[in] finalize_hint`: Optional hint to pass to the finalize callback
+during collection.
+
+Returns `napi_ok` if the API succeeded.
+
+This API associates `data` with the currently running Agent. `data` can later
+be retrieved using `napi_get_instance_data()`. Any existing data associated with
+the currently running Agent which was set by means of a previous call to
+`napi_set_instance_data()` will be overwritten. If a `finalize_cb` was provided
+by the previous call, it will not be called.
+
+### napi_get_instance_data
+<!-- YAML
+added: REPLACEME
+-->
+
+```C
+napi_status napi_get_instance_data(napi_env env,
+ void** data);
+```
+
+- `[in] env`: The environment that the N-API call is invoked under.
+- `[out] data`: The data item that was previously associated with the currently
+running Agent by a call to `napi_set_instance_data()`.
+
+Returns `napi_ok` if the API succeeded.
+
+This API retrieves data that was previously associated with the currently
+running Agent via `napi_set_instance_data()`. If no data is set, the call will
+succeed and `data` will be set to `NULL`.
+
## Basic N-API Data Types
N-API exposes the following fundamental datatypes as abstractions that are
@@ -4876,6 +4952,7 @@ This API may only be called from the main thread.
[Section 6.1.4]: https://tc39.github.io/ecma262/#sec-ecmascript-language-types-string-type
[Section 6.1.6]: https://tc39.github.io/ecma262/#sec-ecmascript-language-types-number-type
[Section 6.1.7.1]: https://tc39.github.io/ecma262/#table-2
+[Section 8.7]: https://tc39.es/ecma262/#sec-agents
[Section 9.1.6]: https://tc39.github.io/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-defineownproperty-p-desc
[Working with JavaScript Functions]: #n_api_working_with_javascript_functions
[Working with JavaScript Properties]: #n_api_working_with_javascript_properties
@@ -4930,3 +5007,4 @@ This API may only be called from the main thread.
[`uv_unref`]: http://docs.libuv.org/en/v1.x/handle.html#c.uv_unref
[async_hooks `type`]: async_hooks.html#async_hooks_type
[context-aware addons]: addons.html#addons_context_aware_addons
+[worker threads]: https://nodejs.org/api/worker_threads.html
diff --git a/src/env.h b/src/env.h
index 4e598a42e5..4eb6575b8f 100644
--- a/src/env.h
+++ b/src/env.h
@@ -152,7 +152,6 @@ constexpr size_t kFsStatsBufferLength =
V(contextify_context_private_symbol, "node:contextify:context") \
V(contextify_global_private_symbol, "node:contextify:global") \
V(decorated_private_symbol, "node:decorated") \
- V(napi_env, "node:napi:env") \
V(napi_wrapper, "node:napi:wrapper") \
V(sab_lifetimepartner_symbol, "node:sharedArrayBufferLifetimePartner") \
diff --git a/src/js_native_api.h b/src/js_native_api.h
index 4f9345a4ab..22cbf4ee30 100644
--- a/src/js_native_api.h
+++ b/src/js_native_api.h
@@ -499,6 +499,15 @@ NAPI_EXTERN napi_status napi_add_finalizer(napi_env env,
napi_finalize finalize_cb,
void* finalize_hint,
napi_ref* result);
+
+// Instance data
+NAPI_EXTERN napi_status napi_set_instance_data(napi_env env,
+ void* data,
+ napi_finalize finalize_cb,
+ void* finalize_hint);
+
+NAPI_EXTERN napi_status napi_get_instance_data(napi_env env,
+ void** data);
#endif // NAPI_EXPERIMENTAL
EXTERN_C_END
diff --git a/src/js_native_api_v8.cc b/src/js_native_api_v8.cc
index 5dac55cc82..fd8728d30f 100644
--- a/src/js_native_api_v8.cc
+++ b/src/js_native_api_v8.cc
@@ -305,12 +305,10 @@ class Reference : private Finalizer {
static void SecondPassCallback(const v8::WeakCallbackInfo<Reference>& data) {
Reference* reference = data.GetParameter();
- napi_env env = reference->_env;
-
if (reference->_finalize_callback != nullptr) {
- NapiCallIntoModuleThrow(env, [&]() {
+ reference->_env->CallIntoModuleThrow([&](napi_env env) {
reference->_finalize_callback(
- reference->_env,
+ env,
reference->_finalize_data,
reference->_finalize_hint);
});
@@ -452,7 +450,9 @@ class CallbackWrapperBase : public CallbackWrapper {
napi_callback cb = _bundle->*FunctionField;
napi_value result;
- NapiCallIntoModuleThrow(env, [&]() { result = cb(env, cbinfo_wrapper); });
+ env->CallIntoModuleThrow([&](napi_env env) {
+ result = cb(env, cbinfo_wrapper);
+ });
if (result != nullptr) {
this->SetReturnValue(result);
@@ -2986,3 +2986,26 @@ napi_status napi_adjust_external_memory(napi_env env,
return napi_clear_last_error(env);
}
+
+napi_status napi_set_instance_data(napi_env env,
+ void* data,
+ napi_finalize finalize_cb,
+ void* finalize_hint) {
+ CHECK_ENV(env);
+
+ env->instance_data.data = data;
+ env->instance_data.finalize_cb = finalize_cb;
+ env->instance_data.hint = finalize_hint;
+
+ return napi_clear_last_error(env);
+}
+
+napi_status napi_get_instance_data(napi_env env,
+ void** data) {
+ CHECK_ENV(env);
+ CHECK_ARG(env, data);
+
+ *data = env->instance_data.data;
+
+ return napi_clear_last_error(env);
+}
diff --git a/src/js_native_api_v8.h b/src/js_native_api_v8.h
index 88c59869a0..506e693f82 100644
--- a/src/js_native_api_v8.h
+++ b/src/js_native_api_v8.h
@@ -6,13 +6,21 @@
#include "js_native_api_types.h"
#include "js_native_api_v8_internals.h"
+static napi_status napi_clear_last_error(napi_env env);
+
struct napi_env__ {
explicit napi_env__(v8::Local<v8::Context> context)
: isolate(context->GetIsolate()),
context_persistent(isolate, context) {
CHECK_EQ(isolate, context->GetIsolate());
}
- virtual ~napi_env__() = default;
+ virtual ~napi_env__() {
+ if (instance_data.finalize_cb != nullptr) {
+ CallIntoModuleThrow([&](napi_env env) {
+ instance_data.finalize_cb(env, instance_data.data, instance_data.hint);
+ });
+ }
+ }
v8::Isolate* const isolate; // Shortcut for context()->GetIsolate()
v8impl::Persistent<v8::Context> context_persistent;
@@ -25,11 +33,37 @@ struct napi_env__ {
virtual bool can_call_into_js() const { return true; }
+ template <typename T, typename U>
+ void CallIntoModule(T&& call, U&& handle_exception) {
+ int open_handle_scopes_before = open_handle_scopes;
+ int open_callback_scopes_before = open_callback_scopes;
+ napi_clear_last_error(this);
+ call(this);
+ CHECK_EQ(open_handle_scopes, open_handle_scopes_before);
+ CHECK_EQ(open_callback_scopes, open_callback_scopes_before);
+ if (!last_exception.IsEmpty()) {
+ handle_exception(this, last_exception.Get(this->isolate));
+ last_exception.Reset();
+ }
+ }
+
+ template <typename T>
+ void CallIntoModuleThrow(T&& call) {
+ CallIntoModule(call, [&](napi_env env, v8::Local<v8::Value> value) {
+ env->isolate->ThrowException(value);
+ });
+ }
+
v8impl::Persistent<v8::Value> last_exception;
napi_extended_error_info last_error;
int open_handle_scopes = 0;
int open_callback_scopes = 0;
int refs = 1;
+ struct {
+ void* data = nullptr;
+ void* hint = nullptr;
+ napi_finalize finalize_cb = nullptr;
+ } instance_data;
};
static inline napi_status napi_clear_last_error(napi_env env) {
@@ -114,27 +148,6 @@ napi_status napi_set_last_error(napi_env env, napi_status error_code,
} \
} while (0)
-template <typename T, typename U>
-void NapiCallIntoModule(napi_env env, T&& call, U&& handle_exception) {
- int open_handle_scopes = env->open_handle_scopes;
- int open_callback_scopes = env->open_callback_scopes;
- napi_clear_last_error(env);
- call();
- CHECK_EQ(env->open_handle_scopes, open_handle_scopes);
- CHECK_EQ(env->open_callback_scopes, open_callback_scopes);
- if (!env->last_exception.IsEmpty()) {
- handle_exception(env->last_exception.Get(env->isolate));
- env->last_exception.Reset();
- }
-}
-
-template <typename T>
-void NapiCallIntoModuleThrow(napi_env env, T&& call) {
- NapiCallIntoModule(env, call, [&](v8::Local<v8::Value> value) {
- env->isolate->ThrowException(value);
- });
-}
-
namespace v8impl {
//=== Conversion between V8 Handles and napi_value ========================
diff --git a/src/node_api.cc b/src/node_api.cc
index a9f26e551d..4947293063 100644
--- a/src/node_api.cc
+++ b/src/node_api.cc
@@ -46,9 +46,9 @@ class BufferFinalizer : private Finalizer {
v8::HandleScope handle_scope(finalizer->_env->isolate);
v8::Context::Scope context_scope(finalizer->_env->context());
- NapiCallIntoModuleThrow(finalizer->_env, [&]() {
+ finalizer->_env->CallIntoModuleThrow([&](napi_env env) {
finalizer->_finalize_callback(
- finalizer->_env,
+ env,
finalizer->_finalize_data,
finalizer->_finalize_hint);
});
@@ -59,44 +59,22 @@ class BufferFinalizer : private Finalizer {
}
};
-static inline napi_env GetEnv(v8::Local<v8::Context> context) {
+static inline napi_env NewEnv(v8::Local<v8::Context> context) {
node_napi_env result;
- auto isolate = context->GetIsolate();
- auto global = context->Global();
-
- // In the case of the string for which we grab the private and the value of
- // the private on the global object we can call .ToLocalChecked() directly
- // because we need to stop hard if either of them is empty.
- //
- // Re https://github.com/nodejs/node/pull/14217#discussion_r128775149
- auto value = global->GetPrivate(context, NAPI_PRIVATE_KEY(context, env))
- .ToLocalChecked();
-
- if (value->IsExternal()) {
- result = static_cast<node_napi_env>(value.As<v8::External>()->Value());
- } else {
- result = new node_napi_env__(context);
- auto external = v8::External::New(isolate, result);
-
- // We must also stop hard if the result of assigning the env to the global
- // is either nothing or false.
- CHECK(global->SetPrivate(context, NAPI_PRIVATE_KEY(context, env), external)
- .FromJust());
-
- // TODO(addaleax): There was previously code that tried to delete the
- // napi_env when its v8::Context was garbage collected;
- // However, as long as N-API addons using this napi_env are in place,
- // the Context needs to be accessible and alive.
- // Ideally, we'd want an on-addon-unload hook that takes care of this
- // once all N-API addons using this napi_env are unloaded.
- // For now, a per-Environment cleanup hook is the best we can do.
- result->node_env()->AddCleanupHook(
- [](void* arg) {
- static_cast<napi_env>(arg)->Unref();
- },
- static_cast<void*>(result));
- }
+ result = new node_napi_env__(context);
+ // TODO(addaleax): There was previously code that tried to delete the
+ // napi_env when its v8::Context was garbage collected;
+ // However, as long as N-API addons using this napi_env are in place,
+ // the Context needs to be accessible and alive.
+ // Ideally, we'd want an on-addon-unload hook that takes care of this
+ // once all N-API addons using this napi_env are unloaded.
+ // For now, a per-Environment cleanup hook is the best we can do.
+ result->node_env()->AddCleanupHook(
+ [](void* arg) {
+ static_cast<napi_env>(arg)->Unref();
+ },
+ static_cast<void*>(result));
return result;
}
@@ -325,7 +303,7 @@ class ThreadSafeFunction : public node::AsyncResource {
v8::Local<v8::Function>::New(env->isolate, ref);
js_callback = v8impl::JsValueFromV8LocalValue(js_cb);
}
- NapiCallIntoModuleThrow(env, [&]() {
+ env->CallIntoModuleThrow([&](napi_env env) {
call_js_cb(env, js_callback, context, data);
});
}
@@ -346,7 +324,7 @@ class ThreadSafeFunction : public node::AsyncResource {
v8::HandleScope scope(env->isolate);
if (finalize_cb) {
CallbackScope cb_scope(this);
- NapiCallIntoModuleThrow(env, [&]() {
+ env->CallIntoModuleThrow([&](napi_env env) {
finalize_cb(env, finalize_data, context);
});
}
@@ -481,10 +459,10 @@ void napi_module_register_by_symbol(v8::Local<v8::Object> exports,
// Create a new napi_env for this module or reference one if a pre-existing
// one is found.
- napi_env env = v8impl::GetEnv(context);
+ napi_env env = v8impl::NewEnv(context);
napi_value _exports;
- NapiCallIntoModuleThrow(env, [&]() {
+ env->CallIntoModuleThrow([&](napi_env env) {
_exports = init(env, v8impl::JsValueFromV8LocalValue(exports));
});
@@ -889,15 +867,9 @@ class Work : public node::AsyncResource, public node::ThreadPoolWork {
CallbackScope callback_scope(this);
- // We have to back up the env here because the `NAPI_CALL_INTO_MODULE` macro
- // makes use of it after the call into the module completes, but the module
- // may have deallocated **this**, and along with it the place where _env is
- // stored.
- napi_env env = _env;
-
- NapiCallIntoModule(env, [&]() {
- _complete(_env, ConvertUVErrorCode(status), _data);
- }, [env](v8::Local<v8::Value> local_err) {
+ _env->CallIntoModule([&](napi_env env) {
+ _complete(env, ConvertUVErrorCode(status), _data);
+ }, [](napi_env env, v8::Local<v8::Value> local_err) {
// If there was an unhandled exception in the complete callback,
// report it as a fatal exception. (There is no JavaScript on the
// callstack that can possibly handle it.)
diff --git a/test/common/require-as.js b/test/common/require-as.js
new file mode 100644
index 0000000000..f55c1a67c4
--- /dev/null
+++ b/test/common/require-as.js
@@ -0,0 +1,28 @@
+/* eslint-disable node-core/require-common-first, node-core/required-modules */
+'use strict';
+
+if (module.parent) {
+ const { spawnSync } = require('child_process');
+
+ function runModuleAs(filename, flags, spawnOptions, role) {
+ return spawnSync(process.execPath,
+ [...flags, __filename, role, filename], spawnOptions);
+ }
+
+ module.exports = runModuleAs;
+ return;
+}
+
+const { Worker, isMainThread, workerData } = require('worker_threads');
+
+if (isMainThread) {
+ if (process.argv[2] === 'worker') {
+ new Worker(__filename, {
+ workerData: process.argv[3]
+ });
+ return;
+ }
+ require(process.argv[3]);
+} else {
+ require(workerData);
+}
diff --git a/test/js-native-api/test_instance_data/binding.gyp b/test/js-native-api/test_instance_data/binding.gyp
new file mode 100644
index 0000000000..5b2d4ff328
--- /dev/null
+++ b/test/js-native-api/test_instance_data/binding.gyp
@@ -0,0 +1,11 @@
+{
+ "targets": [
+ {
+ "target_name": "test_instance_data",
+ "sources": [
+ "../entry_point.c",
+ "test_instance_data.c"
+ ]
+ }
+ ]
+}
diff --git a/test/js-native-api/test_instance_data/test.js b/test/js-native-api/test_instance_data/test.js
new file mode 100644
index 0000000000..986f644fd2
--- /dev/null
+++ b/test/js-native-api/test_instance_data/test.js
@@ -0,0 +1,40 @@
+'use strict';
+// Test API calls for instance data.
+
+const common = require('../../common');
+const assert = require('assert');
+
+if (module.parent) {
+ // When required as a module, run the tests.
+ const test_instance_data =
+ require(`./build/${common.buildType}/test_instance_data`);
+
+ // Print to stdout when the environment deletes the instance data. This output
+ // is checked by the parent process.
+ test_instance_data.setPrintOnDelete();
+
+ // Test that instance data can be accessed from a binding.
+ assert.strictEqual(test_instance_data.increment(), 42);
+
+ // Test that the instance data can be accessed from a finalizer.
+ test_instance_data.objectWithFinalizer(common.mustCall());
+ global.gc();
+} else {
+ // When launched as a script, run tests in either a child process or in a
+ // worker thread.
+ const requireAs = require('../../common/require-as');
+ const runOptions = { stdio: ['inherit', 'pipe', 'inherit'] };
+
+ function checkOutput(child) {
+ assert.strictEqual(child.status, 0);
+ assert.strictEqual(
+ (child.stdout.toString().split(/\r\n?|\n/) || [])[0],
+ 'deleting addon data');
+ }
+
+ // Run tests in a child process.
+ checkOutput(requireAs(__filename, ['--expose-gc'], runOptions, 'child'));
+
+ // Run tests in a worker thread in a child process.
+ checkOutput(requireAs(__filename, ['--expose-gc'], runOptions, 'worker'));
+}
diff --git a/test/js-native-api/test_instance_data/test_instance_data.c b/test/js-native-api/test_instance_data/test_instance_data.c
new file mode 100644
index 0000000000..a64ebec0c1
--- /dev/null
+++ b/test/js-native-api/test_instance_data/test_instance_data.c
@@ -0,0 +1,97 @@
+#include <stdio.h>
+#include <stdlib.h>
+#define NAPI_EXPERIMENTAL
+#include <js_native_api.h>
+#include "../common.h"
+
+typedef struct {
+ size_t value;
+ bool print;
+ napi_ref js_cb_ref;
+} AddonData;
+
+static napi_value Increment(napi_env env, napi_callback_info info) {
+ AddonData* data;
+ napi_value result;
+
+ NAPI_CALL(env, napi_get_instance_data(env, (void**)&data));
+ NAPI_CALL(env, napi_create_uint32(env, ++data->value, &result));
+
+ return result;
+}
+
+static void DeleteAddonData(napi_env env, void* raw_data, void* hint) {
+ AddonData* data = raw_data;
+ if (data->print) {
+ printf("deleting addon data\n");
+ }
+ if (data->js_cb_ref != NULL) {
+ NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, data->js_cb_ref));
+ }
+ free(data);
+}
+
+static napi_value SetPrintOnDelete(napi_env env, napi_callback_info info) {
+ AddonData* data;
+
+ NAPI_CALL(env, napi_get_instance_data(env, (void**)&data));
+ data->print = true;
+
+ return NULL;
+}
+
+static void TestFinalizer(napi_env env, void* raw_data, void* hint) {
+ (void) raw_data;
+ (void) hint;
+
+ AddonData* data;
+ NAPI_CALL_RETURN_VOID(env, napi_get_instance_data(env, (void**)&data));
+ napi_value js_cb, undefined;
+ NAPI_CALL_RETURN_VOID(env,
+ napi_get_reference_value(env, data->js_cb_ref, &js_cb));
+ NAPI_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined));
+ NAPI_CALL_RETURN_VOID(env,
+ napi_call_function(env, undefined, js_cb, 0, NULL, NULL));
+ NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, data->js_cb_ref));
+ data->js_cb_ref = NULL;
+}
+
+static napi_value ObjectWithFinalizer(napi_env env, napi_callback_info info) {
+ AddonData* data;
+ napi_value result, js_cb;
+ size_t argc = 1;
+
+ NAPI_CALL(env, napi_get_instance_data(env, (void**)&data));
+ NAPI_ASSERT(env, data->js_cb_ref == NULL, "reference must be NULL");
+ NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &js_cb, NULL, NULL));
+ NAPI_CALL(env, napi_create_object(env, &result));
+ NAPI_CALL(env,
+ napi_add_finalizer(env, result, NULL, TestFinalizer, NULL, NULL));
+ NAPI_CALL(env, napi_create_reference(env, js_cb, 1, &data->js_cb_ref));
+
+ return result;
+}
+
+EXTERN_C_START
+napi_value Init(napi_env env, napi_value exports) {
+ AddonData* data = malloc(sizeof(*data));
+ data->value = 41;
+ data->print = false;
+ data->js_cb_ref = NULL;
+
+ NAPI_CALL(env, napi_set_instance_data(env, data, DeleteAddonData, NULL));
+
+ napi_property_descriptor props[] = {
+ DECLARE_NAPI_PROPERTY("increment", Increment),
+ DECLARE_NAPI_PROPERTY("setPrintOnDelete", SetPrintOnDelete),
+ DECLARE_NAPI_PROPERTY("objectWithFinalizer", ObjectWithFinalizer),
+ };
+
+ NAPI_CALL(env, napi_define_properties(env,
+ exports,
+ sizeof(props) / sizeof(*props),
+ props));
+
+ return exports;
+}
+EXTERN_C_END
diff --git a/test/node-api/test_env_sharing/binding.gyp b/test/node-api/test_env_sharing/binding.gyp
deleted file mode 100644
index 5699a8391d..0000000000
--- a/test/node-api/test_env_sharing/binding.gyp
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "targets": [
- {
- "target_name": "store_env",
- "sources": [ "store_env.c" ]
- },
- {
- "target_name": "compare_env",
- "sources": [ "compare_env.c" ]
- }
- ]
-}
diff --git a/test/node-api/test_env_sharing/compare_env.c b/test/node-api/test_env_sharing/compare_env.c
deleted file mode 100644
index 0c365f7e34..0000000000
--- a/test/node-api/test_env_sharing/compare_env.c
+++ /dev/null
@@ -1,23 +0,0 @@
-#include <node_api.h>
-#include "../../js-native-api/common.h"
-
-static napi_value compare(napi_env env, napi_callback_info info) {
- napi_value external;
- size_t argc = 1;
- void* data;
- napi_value return_value;
-
- NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &external, NULL, NULL));
- NAPI_CALL(env, napi_get_value_external(env, external, &data));
- NAPI_CALL(env, napi_get_boolean(env, ((napi_env)data) == env, &return_value));
-
- return return_value;
-}
-
-static napi_value Init(napi_env env, napi_value exports) {
- NAPI_CALL(env, napi_create_function(
- env, "exports", NAPI_AUTO_LENGTH, compare, NULL, &exports));
- return exports;
-}
-
-NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
diff --git a/test/node-api/test_env_sharing/store_env.c b/test/node-api/test_env_sharing/store_env.c
deleted file mode 100644
index 6f59cf1fc0..0000000000
--- a/test/node-api/test_env_sharing/store_env.c
+++ /dev/null
@@ -1,10 +0,0 @@
-#include <node_api.h>
-#include "../../js-native-api/common.h"
-
-static napi_value Init(napi_env env, napi_value exports) {
- napi_value external;
- NAPI_CALL(env, napi_create_external(env, env, NULL, NULL, &external));
- return external;
-}
-
-NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
diff --git a/test/node-api/test_env_sharing/test.js b/test/node-api/test_env_sharing/test.js
deleted file mode 100644
index 0a3507177d..0000000000
--- a/test/node-api/test_env_sharing/test.js
+++ /dev/null
@@ -1,9 +0,0 @@
-'use strict';
-
-const common = require('../../common');
-const storeEnv = require(`./build/${common.buildType}/store_env`);
-const compareEnv = require(`./build/${common.buildType}/compare_env`);
-const assert = require('assert');
-
-// N-API environment pointers in two different modules have the same value
-assert.strictEqual(compareEnv(storeEnv), true);
diff --git a/test/node-api/test_instance_data/binding.gyp b/test/node-api/test_instance_data/binding.gyp
new file mode 100644
index 0000000000..0d55905e9e
--- /dev/null
+++ b/test/node-api/test_instance_data/binding.gyp
@@ -0,0 +1,10 @@
+{
+ "targets": [
+ {
+ "target_name": "test_instance_data",
+ "sources": [
+ "test_instance_data.c"
+ ]
+ }
+ ]
+}
diff --git a/test/node-api/test_instance_data/test.js b/test/node-api/test_instance_data/test.js
new file mode 100644
index 0000000000..969c164afd
--- /dev/null
+++ b/test/node-api/test_instance_data/test.js
@@ -0,0 +1,35 @@
+'use strict';
+// Test API calls for instance data.
+
+const common = require('../../common');
+
+if (module.parent) {
+ // When required as a module, run the tests.
+ const test_instance_data =
+ require(`./build/${common.buildType}/test_instance_data`);
+
+ // Test that instance data can be used in an async work callback.
+ new Promise((resolve) => test_instance_data.asyncWorkCallback(resolve))
+
+ // Test that the buffer finalizer can access the instance data.
+ .then(() => new Promise((resolve) => {
+ test_instance_data.testBufferFinalizer(resolve);
+ global.gc();
+ }))
+
+ // Test that the thread-safe function can access the instance data.
+ .then(() => new Promise((resolve) =>
+ test_instance_data.testThreadsafeFunction(common.mustCall(),
+ common.mustCall(resolve))));
+} else {
+ // When launched as a script, run tests in either a child process or in a
+ // worker thread.
+ const requireAs = require('../../common/require-as');
+ const runOptions = { stdio: ['inherit', 'pipe', 'inherit'] };
+
+ // Run tests in a child process.
+ requireAs(__filename, ['--expose-gc'], runOptions, 'child');
+
+ // Run tests in a worker thread in a child process.
+ requireAs(__filename, ['--expose-gc'], runOptions, 'worker');
+}
diff --git a/test/node-api/test_instance_data/test_instance_data.c b/test/node-api/test_instance_data/test_instance_data.c
new file mode 100644
index 0000000000..1a814e91c0
--- /dev/null
+++ b/test/node-api/test_instance_data/test_instance_data.c
@@ -0,0 +1,236 @@
+#include <stdlib.h>
+#include <uv.h>
+#define NAPI_EXPERIMENTAL
+#include <node_api.h>
+#include "../../js-native-api/common.h"
+
+typedef struct {
+ napi_ref js_cb_ref;
+ napi_ref js_tsfn_finalizer_ref;
+ napi_threadsafe_function tsfn;
+ uv_thread_t thread;
+} AddonData;
+
+static void AsyncWorkCbExecute(napi_env env, void* data) {
+ (void) env;
+ (void) data;
+}
+
+static void call_cb_and_delete_ref(napi_env env, napi_ref* optional_ref) {
+ napi_value js_cb, undefined;
+
+ if (optional_ref == NULL) {
+ AddonData* data;
+ NAPI_CALL_RETURN_VOID(env, napi_get_instance_data(env, (void**)&data));
+ optional_ref = &data->js_cb_ref;
+ }
+
+ NAPI_CALL_RETURN_VOID(env, napi_get_reference_value(env,
+ *optional_ref,
+ &js_cb));
+ NAPI_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined));
+ NAPI_CALL_RETURN_VOID(env, napi_call_function(env,
+ undefined,
+ js_cb,
+ 0,
+ NULL,
+ NULL));
+ NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, *optional_ref));
+
+ *optional_ref = NULL;
+}
+
+static void AsyncWorkCbComplete(napi_env env,
+ napi_status status,
+ void* data) {
+ (void) status;
+ (void) data;
+ call_cb_and_delete_ref(env, NULL);
+}
+
+static bool establish_callback_ref(napi_env env, napi_callback_info info) {
+ AddonData* data;
+ size_t argc = 1;
+ napi_value js_cb;
+
+ NAPI_CALL_BASE(env, napi_get_instance_data(env, (void**)&data), false);
+ NAPI_ASSERT_BASE(env,
+ data->js_cb_ref == NULL,
+ "reference must be NULL",
+ false);
+ NAPI_CALL_BASE(env,
+ napi_get_cb_info(env, info, &argc, &js_cb, NULL, NULL),
+ false);
+ NAPI_CALL_BASE(env,
+ napi_create_reference(env, js_cb, 1, &data->js_cb_ref),
+ false);
+
+ return true;
+}
+
+static napi_value AsyncWorkCallback(napi_env env, napi_callback_info info) {
+ if (establish_callback_ref(env, info)) {
+ napi_value resource_name;
+ napi_async_work work;
+
+ NAPI_CALL(env, napi_create_string_utf8(env,
+ "AsyncIncrement",
+ NAPI_AUTO_LENGTH,
+ &resource_name));
+ NAPI_CALL(env, napi_create_async_work(env,
+ NULL,
+ resource_name,
+ AsyncWorkCbExecute,
+ AsyncWorkCbComplete,
+ NULL,
+ &work));
+ NAPI_CALL(env, napi_queue_async_work(env, work));
+ }
+
+ return NULL;
+}
+
+static void TestBufferFinalizerCallback(napi_env env, void* data, void* hint) {
+ (void) data;
+ (void) hint;
+ call_cb_and_delete_ref(env, NULL);
+}
+
+static napi_value TestBufferFinalizer(napi_env env, napi_callback_info info) {
+ napi_value buffer = NULL;
+ if (establish_callback_ref(env, info)) {
+ NAPI_CALL(env, napi_create_external_buffer(env,
+ sizeof(napi_callback),
+ TestBufferFinalizer,
+ TestBufferFinalizerCallback,
+ NULL,
+ &buffer));
+ }
+ return buffer;
+}
+
+static void ThreadsafeFunctionCallJS(napi_env env,
+ napi_value tsfn_cb,
+ void* context,
+ void* data) {
+ (void) tsfn_cb;
+ (void) context;
+ (void) data;
+ call_cb_and_delete_ref(env, NULL);
+}
+
+static void ThreadsafeFunctionTestThread(void* raw_data) {
+ AddonData* data = raw_data;
+ napi_status status;
+
+ // No need to call `napi_acquire_threadsafe_function()` because the main
+ // thread has set the refcount to 1 and there is only this one secondary
+ // thread.
+ status = napi_call_threadsafe_function(data->tsfn,
+ ThreadsafeFunctionCallJS,
+ napi_tsfn_nonblocking);
+ if (status != napi_ok) {
+ napi_fatal_error("ThreadSafeFunctionTestThread",
+ NAPI_AUTO_LENGTH,
+ "Failed to call TSFN",
+ NAPI_AUTO_LENGTH);
+ }
+
+ status = napi_release_threadsafe_function(data->tsfn, napi_tsfn_release);
+ if (status != napi_ok) {
+ napi_fatal_error("ThreadSafeFunctionTestThread",
+ NAPI_AUTO_LENGTH,
+ "Failed to release TSFN",
+ NAPI_AUTO_LENGTH);
+ }
+
+}
+
+static void FinalizeThreadsafeFunction(napi_env env, void* raw, void* hint) {
+ AddonData* data;
+ NAPI_CALL_RETURN_VOID(env, napi_get_instance_data(env, (void**)&data));
+ NAPI_ASSERT_RETURN_VOID(env,
+ uv_thread_join(&data->thread) == 0,
+ "Failed to join the thread");
+ call_cb_and_delete_ref(env, &data->js_tsfn_finalizer_ref);
+ data->tsfn = NULL;
+}
+
+// Ths function accepts two arguments: the JS callback, and the finalize
+// callback. The latter moves the test forward.
+static napi_value
+TestThreadsafeFunction(napi_env env, napi_callback_info info) {
+ AddonData* data;
+ size_t argc = 2;
+ napi_value argv[2], resource_name;
+
+ NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
+ NAPI_CALL(env, napi_get_instance_data(env, (void**)&data));
+ NAPI_ASSERT(env, data->js_cb_ref == NULL, "reference must be NULL");
+ NAPI_ASSERT(env,
+ data->js_tsfn_finalizer_ref == NULL,
+ "tsfn finalizer reference must be NULL");
+ NAPI_CALL(env, napi_create_reference(env, argv[0], 1, &data->js_cb_ref));
+ NAPI_CALL(env, napi_create_reference(env,
+ argv[1],
+ 1,
+ &data->js_tsfn_finalizer_ref));
+ NAPI_CALL(env, napi_create_string_utf8(env,
+ "TSFN instance data test",
+ NAPI_AUTO_LENGTH,
+ &resource_name));
+ NAPI_CALL(env, napi_create_threadsafe_function(env,
+ NULL,
+ NULL,
+ resource_name,
+ 0,
+ 1,
+ NULL,
+ FinalizeThreadsafeFunction,
+ NULL,
+ ThreadsafeFunctionCallJS,
+ &data->tsfn));
+ NAPI_ASSERT(env,
+ uv_thread_create(&data->thread,
+ ThreadsafeFunctionTestThread,
+ data) == 0,
+ "uv_thread_create failed");
+
+ return NULL;
+}
+
+static void DeleteAddonData(napi_env env, void* raw_data, void* hint) {
+ AddonData* data = raw_data;
+ if (data->js_cb_ref) {
+ NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, data->js_cb_ref));
+ }
+ if (data->js_tsfn_finalizer_ref) {
+ NAPI_CALL_RETURN_VOID(env,
+ napi_delete_reference(env,
+ data->js_tsfn_finalizer_ref));
+ }
+ free(data);
+}
+
+static napi_value Init(napi_env env, napi_value exports) {
+ AddonData* data = malloc(sizeof(*data));
+ data->js_cb_ref = NULL;
+ data->js_tsfn_finalizer_ref = NULL;
+
+ NAPI_CALL(env, napi_set_instance_data(env, data, DeleteAddonData, NULL));
+
+ napi_property_descriptor props[] = {
+ DECLARE_NAPI_PROPERTY("asyncWorkCallback", AsyncWorkCallback),
+ DECLARE_NAPI_PROPERTY("testBufferFinalizer", TestBufferFinalizer),
+ DECLARE_NAPI_PROPERTY("testThreadsafeFunction", TestThreadsafeFunction),
+ };
+
+ NAPI_CALL(env, napi_define_properties(env,
+ exports,
+ sizeof(props) / sizeof(*props),
+ props));
+
+ return exports;
+}
+
+NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)