n-api: add APIs for per-instance state management
Adds `napi_set_instance_data()` and `napi_get_instance_data()`, which allow native addons to store their data on and retrieve their data from `napi_env`. `napi_set_instance_data()` accepts a finalizer which is called when the `node::Environment()` is destroyed. This entails rendering the `napi_env` local to each add-on. Fixes: https://github.com/nodejs/abi-stable-node/issues/378 PR-URL: https://github.com/nodejs/node/pull/28682 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
This commit is contained in:
parent
e9ea8eaf7a
commit
5030e81ce3
@ -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
|
## Basic N-API Data Types
|
||||||
|
|
||||||
N-API exposes the following fundamental datatypes as abstractions that are
|
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.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.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 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
|
[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 Functions]: #n_api_working_with_javascript_functions
|
||||||
[Working with JavaScript Properties]: #n_api_working_with_javascript_properties
|
[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
|
[`uv_unref`]: http://docs.libuv.org/en/v1.x/handle.html#c.uv_unref
|
||||||
[async_hooks `type`]: async_hooks.html#async_hooks_type
|
[async_hooks `type`]: async_hooks.html#async_hooks_type
|
||||||
[context-aware addons]: addons.html#addons_context_aware_addons
|
[context-aware addons]: addons.html#addons_context_aware_addons
|
||||||
|
[worker threads]: https://nodejs.org/api/worker_threads.html
|
||||||
|
@ -152,7 +152,6 @@ constexpr size_t kFsStatsBufferLength =
|
|||||||
V(contextify_context_private_symbol, "node:contextify:context") \
|
V(contextify_context_private_symbol, "node:contextify:context") \
|
||||||
V(contextify_global_private_symbol, "node:contextify:global") \
|
V(contextify_global_private_symbol, "node:contextify:global") \
|
||||||
V(decorated_private_symbol, "node:decorated") \
|
V(decorated_private_symbol, "node:decorated") \
|
||||||
V(napi_env, "node:napi:env") \
|
|
||||||
V(napi_wrapper, "node:napi:wrapper") \
|
V(napi_wrapper, "node:napi:wrapper") \
|
||||||
V(sab_lifetimepartner_symbol, "node:sharedArrayBufferLifetimePartner") \
|
V(sab_lifetimepartner_symbol, "node:sharedArrayBufferLifetimePartner") \
|
||||||
|
|
||||||
|
@ -499,6 +499,15 @@ NAPI_EXTERN napi_status napi_add_finalizer(napi_env env,
|
|||||||
napi_finalize finalize_cb,
|
napi_finalize finalize_cb,
|
||||||
void* finalize_hint,
|
void* finalize_hint,
|
||||||
napi_ref* result);
|
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
|
#endif // NAPI_EXPERIMENTAL
|
||||||
|
|
||||||
EXTERN_C_END
|
EXTERN_C_END
|
||||||
|
@ -305,12 +305,10 @@ class Reference : private Finalizer {
|
|||||||
static void SecondPassCallback(const v8::WeakCallbackInfo<Reference>& data) {
|
static void SecondPassCallback(const v8::WeakCallbackInfo<Reference>& data) {
|
||||||
Reference* reference = data.GetParameter();
|
Reference* reference = data.GetParameter();
|
||||||
|
|
||||||
napi_env env = reference->_env;
|
|
||||||
|
|
||||||
if (reference->_finalize_callback != nullptr) {
|
if (reference->_finalize_callback != nullptr) {
|
||||||
NapiCallIntoModuleThrow(env, [&]() {
|
reference->_env->CallIntoModuleThrow([&](napi_env env) {
|
||||||
reference->_finalize_callback(
|
reference->_finalize_callback(
|
||||||
reference->_env,
|
env,
|
||||||
reference->_finalize_data,
|
reference->_finalize_data,
|
||||||
reference->_finalize_hint);
|
reference->_finalize_hint);
|
||||||
});
|
});
|
||||||
@ -452,7 +450,9 @@ class CallbackWrapperBase : public CallbackWrapper {
|
|||||||
napi_callback cb = _bundle->*FunctionField;
|
napi_callback cb = _bundle->*FunctionField;
|
||||||
|
|
||||||
napi_value result;
|
napi_value result;
|
||||||
NapiCallIntoModuleThrow(env, [&]() { result = cb(env, cbinfo_wrapper); });
|
env->CallIntoModuleThrow([&](napi_env env) {
|
||||||
|
result = cb(env, cbinfo_wrapper);
|
||||||
|
});
|
||||||
|
|
||||||
if (result != nullptr) {
|
if (result != nullptr) {
|
||||||
this->SetReturnValue(result);
|
this->SetReturnValue(result);
|
||||||
@ -2986,3 +2986,26 @@ napi_status napi_adjust_external_memory(napi_env env,
|
|||||||
|
|
||||||
return napi_clear_last_error(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);
|
||||||
|
}
|
||||||
|
@ -6,13 +6,21 @@
|
|||||||
#include "js_native_api_types.h"
|
#include "js_native_api_types.h"
|
||||||
#include "js_native_api_v8_internals.h"
|
#include "js_native_api_v8_internals.h"
|
||||||
|
|
||||||
|
static napi_status napi_clear_last_error(napi_env env);
|
||||||
|
|
||||||
struct napi_env__ {
|
struct napi_env__ {
|
||||||
explicit napi_env__(v8::Local<v8::Context> context)
|
explicit napi_env__(v8::Local<v8::Context> context)
|
||||||
: isolate(context->GetIsolate()),
|
: isolate(context->GetIsolate()),
|
||||||
context_persistent(isolate, context) {
|
context_persistent(isolate, context) {
|
||||||
CHECK_EQ(isolate, context->GetIsolate());
|
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()
|
v8::Isolate* const isolate; // Shortcut for context()->GetIsolate()
|
||||||
v8impl::Persistent<v8::Context> context_persistent;
|
v8impl::Persistent<v8::Context> context_persistent;
|
||||||
|
|
||||||
@ -25,11 +33,37 @@ struct napi_env__ {
|
|||||||
|
|
||||||
virtual bool can_call_into_js() const { return true; }
|
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;
|
v8impl::Persistent<v8::Value> last_exception;
|
||||||
napi_extended_error_info last_error;
|
napi_extended_error_info last_error;
|
||||||
int open_handle_scopes = 0;
|
int open_handle_scopes = 0;
|
||||||
int open_callback_scopes = 0;
|
int open_callback_scopes = 0;
|
||||||
int refs = 1;
|
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) {
|
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)
|
} 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 {
|
namespace v8impl {
|
||||||
|
|
||||||
//=== Conversion between V8 Handles and napi_value ========================
|
//=== Conversion between V8 Handles and napi_value ========================
|
||||||
|
@ -46,9 +46,9 @@ class BufferFinalizer : private Finalizer {
|
|||||||
v8::HandleScope handle_scope(finalizer->_env->isolate);
|
v8::HandleScope handle_scope(finalizer->_env->isolate);
|
||||||
v8::Context::Scope context_scope(finalizer->_env->context());
|
v8::Context::Scope context_scope(finalizer->_env->context());
|
||||||
|
|
||||||
NapiCallIntoModuleThrow(finalizer->_env, [&]() {
|
finalizer->_env->CallIntoModuleThrow([&](napi_env env) {
|
||||||
finalizer->_finalize_callback(
|
finalizer->_finalize_callback(
|
||||||
finalizer->_env,
|
env,
|
||||||
finalizer->_finalize_data,
|
finalizer->_finalize_data,
|
||||||
finalizer->_finalize_hint);
|
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;
|
node_napi_env result;
|
||||||
|
|
||||||
auto isolate = context->GetIsolate();
|
result = new node_napi_env__(context);
|
||||||
auto global = context->Global();
|
// TODO(addaleax): There was previously code that tried to delete the
|
||||||
|
// napi_env when its v8::Context was garbage collected;
|
||||||
// In the case of the string for which we grab the private and the value of
|
// However, as long as N-API addons using this napi_env are in place,
|
||||||
// the private on the global object we can call .ToLocalChecked() directly
|
// the Context needs to be accessible and alive.
|
||||||
// because we need to stop hard if either of them is empty.
|
// 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.
|
||||||
// Re https://github.com/nodejs/node/pull/14217#discussion_r128775149
|
// For now, a per-Environment cleanup hook is the best we can do.
|
||||||
auto value = global->GetPrivate(context, NAPI_PRIVATE_KEY(context, env))
|
result->node_env()->AddCleanupHook(
|
||||||
.ToLocalChecked();
|
[](void* arg) {
|
||||||
|
static_cast<napi_env>(arg)->Unref();
|
||||||
if (value->IsExternal()) {
|
},
|
||||||
result = static_cast<node_napi_env>(value.As<v8::External>()->Value());
|
static_cast<void*>(result));
|
||||||
} 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));
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -325,7 +303,7 @@ class ThreadSafeFunction : public node::AsyncResource {
|
|||||||
v8::Local<v8::Function>::New(env->isolate, ref);
|
v8::Local<v8::Function>::New(env->isolate, ref);
|
||||||
js_callback = v8impl::JsValueFromV8LocalValue(js_cb);
|
js_callback = v8impl::JsValueFromV8LocalValue(js_cb);
|
||||||
}
|
}
|
||||||
NapiCallIntoModuleThrow(env, [&]() {
|
env->CallIntoModuleThrow([&](napi_env env) {
|
||||||
call_js_cb(env, js_callback, context, data);
|
call_js_cb(env, js_callback, context, data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -346,7 +324,7 @@ class ThreadSafeFunction : public node::AsyncResource {
|
|||||||
v8::HandleScope scope(env->isolate);
|
v8::HandleScope scope(env->isolate);
|
||||||
if (finalize_cb) {
|
if (finalize_cb) {
|
||||||
CallbackScope cb_scope(this);
|
CallbackScope cb_scope(this);
|
||||||
NapiCallIntoModuleThrow(env, [&]() {
|
env->CallIntoModuleThrow([&](napi_env env) {
|
||||||
finalize_cb(env, finalize_data, context);
|
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
|
// Create a new napi_env for this module or reference one if a pre-existing
|
||||||
// one is found.
|
// one is found.
|
||||||
napi_env env = v8impl::GetEnv(context);
|
napi_env env = v8impl::NewEnv(context);
|
||||||
|
|
||||||
napi_value _exports;
|
napi_value _exports;
|
||||||
NapiCallIntoModuleThrow(env, [&]() {
|
env->CallIntoModuleThrow([&](napi_env env) {
|
||||||
_exports = init(env, v8impl::JsValueFromV8LocalValue(exports));
|
_exports = init(env, v8impl::JsValueFromV8LocalValue(exports));
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -889,15 +867,9 @@ class Work : public node::AsyncResource, public node::ThreadPoolWork {
|
|||||||
|
|
||||||
CallbackScope callback_scope(this);
|
CallbackScope callback_scope(this);
|
||||||
|
|
||||||
// We have to back up the env here because the `NAPI_CALL_INTO_MODULE` macro
|
_env->CallIntoModule([&](napi_env env) {
|
||||||
// makes use of it after the call into the module completes, but the module
|
_complete(env, ConvertUVErrorCode(status), _data);
|
||||||
// may have deallocated **this**, and along with it the place where _env is
|
}, [](napi_env env, v8::Local<v8::Value> local_err) {
|
||||||
// stored.
|
|
||||||
napi_env env = _env;
|
|
||||||
|
|
||||||
NapiCallIntoModule(env, [&]() {
|
|
||||||
_complete(_env, ConvertUVErrorCode(status), _data);
|
|
||||||
}, [env](v8::Local<v8::Value> local_err) {
|
|
||||||
// If there was an unhandled exception in the complete callback,
|
// If there was an unhandled exception in the complete callback,
|
||||||
// report it as a fatal exception. (There is no JavaScript on the
|
// report it as a fatal exception. (There is no JavaScript on the
|
||||||
// callstack that can possibly handle it.)
|
// callstack that can possibly handle it.)
|
||||||
|
28
test/common/require-as.js
Normal file
28
test/common/require-as.js
Normal file
@ -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);
|
||||||
|
}
|
11
test/js-native-api/test_instance_data/binding.gyp
Normal file
11
test/js-native-api/test_instance_data/binding.gyp
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"target_name": "test_instance_data",
|
||||||
|
"sources": [
|
||||||
|
"../entry_point.c",
|
||||||
|
"test_instance_data.c"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
40
test/js-native-api/test_instance_data/test.js
Normal file
40
test/js-native-api/test_instance_data/test.js
Normal file
@ -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'));
|
||||||
|
}
|
97
test/js-native-api/test_instance_data/test_instance_data.c
Normal file
97
test/js-native-api/test_instance_data/test_instance_data.c
Normal file
@ -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
|
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"target_name": "store_env",
|
|
||||||
"sources": [ "store_env.c" ]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"target_name": "compare_env",
|
|
||||||
"sources": [ "compare_env.c" ]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -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)
|
|
@ -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)
|
|
@ -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);
|
|
10
test/node-api/test_instance_data/binding.gyp
Normal file
10
test/node-api/test_instance_data/binding.gyp
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"target_name": "test_instance_data",
|
||||||
|
"sources": [
|
||||||
|
"test_instance_data.c"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
35
test/node-api/test_instance_data/test.js
Normal file
35
test/node-api/test_instance_data/test.js
Normal file
@ -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');
|
||||||
|
}
|
236
test/node-api/test_instance_data/test_instance_data.c
Normal file
236
test/node-api/test_instance_data/test_instance_data.c
Normal file
@ -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)
|
Loading…
x
Reference in New Issue
Block a user