You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
There does not seem to be a way to initialise a context-aware module with node-addon-api currently.
Additionally it seems to me that it would be nice to provide a default wrapper for per-context global state (what would normally be easily accomplished with static variables).
Fwiw, all N-API addons are considered context-aware.
Additionally it seems to me that it would be nice to provide a default wrapper for per-context global state (what would normally be easily accomplished with static variables).
I think napi_set_instance_data might be what you want – it’s technically behind the NAPI_EXPERIMENTAL flag, but you can use it if you’re targeting the latest version of Node.js.
(The documentation for it is kind of misleading imo – Node.js currently does not support loading addons from multiple vm Contexts, so right now it’s not wrong to say that the data is associated with the current agent, but conceptually, the association is with the current module instantiation, which would be per-Context and not per-Agent.)
It does seem like napi_set_instance_data/napi_get_instance_data will provide a good solution. So all that is required is a nice pattern to initialise a module, associate a C++ class with the instance data and support retrieving it from a given Napi::Env handle.
Is there any way to polyfill this for Node 10? Would be a shame to have to wait for Node 10 to be out of LTS for it to be generally usable for add-ons.
@gabrielschulhof I think you implemented napi_set_instance_date/napi_get_instance_data. I also remember that we refactored the internal structure at some point. Do you remember if that was before/after 10.x as that might affect how easily those two functions can be supported in the 10.x line.
@rolftimmermans there are several solutions to attaching data to an addon instance, in increasing order of correctness and convenience, but decreasing availability in terms of older Node.js versions:
Use napi_wrap() to attach data to exports to find out when to delete the per-instance data, and pass the data pointer to each binding created with Napi::Function::New(), which has a parameter void* data.
This can be done with the earliest version of Node.js, but will not actually free the data until the version that ships with n-api: render finalizers as env cleanup hooks node#28428, because exports is never actually garbage-collected.
Use napi_add_env_cleanup_hook() and pass the data pointer to each binding created with Napi::Function::New(). This is also available down to v8.12.0, and will actually be called when the environment exits.
Use napi_set_instance_data() and napi_get_instance_data(). This is most convenient because you need not pass a void* data to each function you create, but this is only available as of v12.8.0.
I agree that we could use a nice abstraction on top of these mechanisms to render addons as instance factories.
Thanks for clearing this up! Very helpful. I see that ObjectWrap<T>::DefineClass also takes a void* argument which means that together with Function::New it appears to solve this problem.
So it turns out that napi_add_env_cleanup_hook() works really well for this use case, but there are more issues that are solved by nodejs/node#28428. Would be really nice if this fix can make it into Node 12 LTS.
I have gone down the napi_add_env_cleanup_hook() route, but am now figuring out that it seems impossible to detect if the env that is being cleaned up is the main thread (at process exit) or a worker thread. Apart from some thread/agent-specific state, the module I'm working on also has some shared, thread-safe, truly global state that I want to tear down at process exit.
I guess It's just not possible right now to detect when the module is loaded/released from the main thread, correct?
I have attempted to use static destructors for this cleanup but it does not seem to work correctly on windows.
@addaleax how safe is it to assume that the relationship between the environment and the thread is 1:1?
@rolftimmermans could you use thread-safe refcounting to decide when to tear down the per-process state? I'm fairly certain that the main thread is the last one to exit.
Alright, the following is a summary of I have now, based on your comments. It might be useful for others.
This seems to work well for Node 10+, except that it appears nodejs/node#28428 won't be included in a stable version until Node 14, which means that so far any use of threads with native addons is giant memory leak (I haven't found a reliable workaround for this unfortunately – suggestions welcome!).
classGlobalState {
// ... Globally shared, thread-safe state.// e.g. uint64_t FooBar;public:static std::shared_ptr<GlobalState> Instance() {
static std::mutex lock;
static std::weak_ptr<GlobalState> shared;
std::lock_guard<std::mutex> guard(lock);
// Get an existing instance from the weak reference, if possible.if (auto instance = shared.lock()) {
return instance;
}
// Create a new instance and keep a weak reference.// Global state will be cleaned up when last thread exits.auto instance = std::make_shared<ContextReaper>();
shared = instance;
return instance;
}
};
classModule {
// Pointer to shared global state
std::shared_ptr<GlobalState> global_state = GlobalState::Instance();
// ... Other per-module state// e.g.: Napi::FunctionReference foo_constructor;public:
GlobalState& GlobalState() {
return *global_state;
}
Module(Napi::Object exports) {
// Initialization per module, assigns constructors, etc.auto proto = {
// ... InstanceMethod("foo", &Foo::foo),
};
// Pass module pointer as last argument.DefineClass(exports.Env(), "Foo", proto, this);
}
};
classFoo : publicNapi::ObjectWrap<Foo> {
Module& module;
public:// Construct native object. `info.Data()` contains pointer to module.Foo(const Napi::CallbackInfo& info)
: Napi::ObjectWrap<Foo>(info),
module(*reinterpret_cast<Module*>(info.Data())) {
// Access global state with e.g. `module.GlobalState().FooBar`
}
}
NAPI_MODULE_INIT(/* env, exports */) {
automodule = newModule(Napi::Object(env, exports));
auto terminate = [](void* data) { deletereinterpret_cast<Module*>(data); };
auto status = napi_add_env_cleanup_hook(env, terminate, module);
assert(status == napi_ok);
return exports;
}
In the future the module-wide state could be accessed via Napi::Environment if it wraps napi_set_instance_data/napi_get_instance_data, and that means the pointer to the module does not have to be passed during class definition, correct?
@addaleax how safe is it to assume that the relationship between the environment and the thread is 1:1?
@gabrielschulhof This is currently always true for the Node.js release binary/when used with Workers, but native addons or embedders don’t need to follow any rules regarding relationships between threads and Environment instances. I would avoid relying on any relationship of that kind as far as possible.
Apart from some thread/agent-specific state, the module I'm working on also has some shared, thread-safe, truly global state that I want to tear down at process exit.
@rolftimmermans If you are looking for “global” state that should be torn down when your addon is unloaded, then static data in your addon code should be the way to go.
This seems to work well for Node 10+, except that it appears nodejs/node#28428 won't be included in a stable version until Node 14
Why do you think it won’t be included into Node 13 and backported to earlier release lines? While I certainly think we should be cautious about that, I think this is what we should be doing.
which means that so far any use of threads with native addons is giant memory leak (I haven't found a reliable workaround for this unfortunately – suggestions welcome!).
Yeah, that’s just a really, really unfortunate fact in terms of N-API’s design. You should be able to use napi_add_env_cleanup_hook in order to emulate the behaviour introduced in nodejs/node#28428, but that’s going to be a lot of manual work unless node-addon-api implements it for all references on its own.
Why do you think it won’t be included into Node 13 and backported to earlier release lines? While I certainly think we should be cautious about that, I think this is what we should be doing.
@addaleax It would be nice if it could be back-ported, definitely!
Yeah, that’s just a really, really unfortunate fact in terms of N-API’s design. You should be able to use napi_add_env_cleanup_hook in order to emulate the behaviour introduced in nodejs/node#28428, but that’s going to be a lot of manual work unless node-addon-api implements it for all references on its own.
Having it in node-addon-api would be ideal. Maybe with a compile time flag if it has any runtime overhead. If I have time, I might try to work on an implementation. Any tips how to approach this would be welcome!
If you are looking for “global” state that should be torn down when your addon is unloaded, then static data in your addon code should be the way to go.
I ran into some trouble when using static globals that are initialized in the before-main time on windows machines, at least while using cmakejs instead of gyp: cmake-js/cmake-js#205 , however static local variables are working fine, so I recommend to use those.
However the whole context aware addon problematic came to my attention just of today, so my bug might be related to my probale misunderstanding of this.
Correct me if I am wrong: Since the whole shared library is loaded only once, at the electron app seems to be just one node-process (but multiple envs/worker threads /agents), I could use per process globals like static local variables for whole process granularity, as long as I do in a thread safe matter, right? Only draw back, would be the missing clean up. But at least I could have a global USB Device context, and queue the commands issued by my JS code.
@Superlokkus additionally, if you need per-addon-instance data, you can use napi_set_instance_data() and napi_get_instance_data() and you will get cleanup whenever an instance of your addon is unloaded. These are core N-APIs, but we're making them available in node-addon-api v3.0.0 (to be released soon) under Napi::Env::SetInstanceData() and Napi::Env::GetInstanceData().
This issue is stale because it has been open many days with no activity. It will be closed soon unless the stale label is removed or a comment is made.
Dear All,
This thread i think deals with some what the issue we are facing currenty.
Our requirement is that using napi addon we are trying to load core-network shared library , we are attaching callback to core-network shared library ,whenever there is change in network status , when our callback is hit ,we want to call the JS layer from the addon.
Scenario 1.
JS-Layer-------------->napi addon--->calling apis of--->network core library
Getting Result at JS<---<Return result--------------------------------return result
This is running fine for us and we are able to get the response data in JS laye
Scenario 2.
JS-Layer-------------->napi addon--->registering cabllback -- --->network core library
<????????---------callback invoked----------------------------calling callback function
Here in Scenario 2. we are facing issue in getting the napi env and calling any JS function.
Activity
addaleax commentedon Oct 16, 2019
Fwiw, all N-API addons are considered context-aware.
I think
napi_set_instance_datamight be what you want – it’s technically behind theNAPI_EXPERIMENTALflag, but you can use it if you’re targeting the latest version of Node.js.(The documentation for it is kind of misleading imo – Node.js currently does not support loading addons from multiple vm Contexts, so right now it’s not wrong to say that the data is associated with the current agent, but conceptually, the association is with the current module instantiation, which would be per-Context and not per-Agent.)
rolftimmermans commentedon Oct 16, 2019
It does seem like
napi_set_instance_data/napi_get_instance_datawill provide a good solution. So all that is required is a nice pattern to initialise a module, associate a C++ class with the instance data and support retrieving it from a givenNapi::Envhandle.Is there any way to polyfill this for Node 10? Would be a shame to have to wait for Node 10 to be out of LTS for it to be generally usable for add-ons.
mhdawson commentedon Oct 17, 2019
@gabrielschulhof I think you implemented napi_set_instance_date/napi_get_instance_data. I also remember that we refactored the internal structure at some point. Do you remember if that was before/after 10.x as that might affect how easily those two functions can be supported in the 10.x line.
gabrielschulhof commentedon Oct 17, 2019
@rolftimmermans there are several solutions to attaching data to an addon instance, in increasing order of correctness and convenience, but decreasing availability in terms of older Node.js versions:
napi_wrap()to attach data toexportsto find out when to delete the per-instance data, and pass the data pointer to each binding created withNapi::Function::New(), which has a parametervoid* data.This can be done with the earliest version of Node.js, but will not actually free the data until the version that ships with n-api: render finalizers as env cleanup hooks node#28428, because
exportsis never actually garbage-collected.napi_add_env_cleanup_hook()and pass the data pointer to each binding created withNapi::Function::New(). This is also available down to v8.12.0, and will actually be called when the environment exits.napi_set_instance_data()andnapi_get_instance_data(). This is most convenient because you need not pass avoid* datato each function you create, but this is only available as of v12.8.0.I agree that we could use a nice abstraction on top of these mechanisms to render addons as instance factories.
gabrielschulhof commentedon Oct 17, 2019
whew ... I did several edits to my comment after I wrote it. Please do not read the emailed version 😀
rolftimmermans commentedon Oct 17, 2019
Thanks for clearing this up! Very helpful. I see that
ObjectWrap<T>::DefineClassalso takes avoid*argument which means that together withFunction::Newit appears to solve this problem.rolftimmermans commentedon Oct 18, 2019
So it turns out that
napi_add_env_cleanup_hook()works really well for this use case, but there are more issues that are solved by nodejs/node#28428. Would be really nice if this fix can make it into Node 12 LTS.rolftimmermans commentedon Oct 19, 2019
I have gone down the
napi_add_env_cleanup_hook()route, but am now figuring out that it seems impossible to detect if the env that is being cleaned up is the main thread (at process exit) or a worker thread. Apart from some thread/agent-specific state, the module I'm working on also has some shared, thread-safe, truly global state that I want to tear down at process exit.I guess It's just not possible right now to detect when the module is loaded/released from the main thread, correct?
I have attempted to use static destructors for this cleanup but it does not seem to work correctly on windows.
gabrielschulhof commentedon Oct 21, 2019
@addaleax how safe is it to assume that the relationship between the environment and the thread is 1:1?
@rolftimmermans could you use thread-safe refcounting to decide when to tear down the per-process state? I'm fairly certain that the main thread is the last one to exit.
devsnek commentedon Oct 21, 2019
You can have multiple napi environments in a single thread, but you can't have multiple threads interacting with a napi environment.
gabrielschulhof commentedon Oct 21, 2019
@addaleax @devsnek In my previous question I was thinking about
Node::Environments, actually, notnapi_envs. Sorry about not being explicit!rolftimmermans commentedon Oct 22, 2019
Alright, the following is a summary of I have now, based on your comments. It might be useful for others.
This seems to work well for Node 10+, except that it appears nodejs/node#28428 won't be included in a stable version until Node 14, which means that so far any use of threads with native addons is giant memory leak (I haven't found a reliable workaround for this unfortunately – suggestions welcome!).
In the future the module-wide state could be accessed via
Napi::Environmentif it wrapsnapi_set_instance_data/napi_get_instance_data, and that means the pointer to the module does not have to be passed during class definition, correct?addaleax commentedon Oct 22, 2019
@gabrielschulhof This is currently always true for the Node.js release binary/when used with Workers, but native addons or embedders don’t need to follow any rules regarding relationships between threads and
Environmentinstances. I would avoid relying on any relationship of that kind as far as possible.@rolftimmermans If you are looking for “global” state that should be torn down when your addon is unloaded, then
staticdata in your addon code should be the way to go.Why do you think it won’t be included into Node 13 and backported to earlier release lines? While I certainly think we should be cautious about that, I think this is what we should be doing.
Yeah, that’s just a really, really unfortunate fact in terms of N-API’s design. You should be able to use
napi_add_env_cleanup_hookin order to emulate the behaviour introduced in nodejs/node#28428, but that’s going to be a lot of manual work unlessnode-addon-apiimplements it for all references on its own.rolftimmermans commentedon Oct 22, 2019
@addaleax It would be nice if it could be back-ported, definitely!
Having it in
node-addon-apiwould be ideal. Maybe with a compile time flag if it has any runtime overhead. If I have time, I might try to work on an implementation. Any tips how to approach this would be welcome!gabrielschulhof commentedon Oct 23, 2019
@rolftimmermans nodejs/node#30070
1 remaining item
Superlokkus commentedon Apr 23, 2020
I ran into some trouble when using static globals that are initialized in the before-main time on windows machines, at least while using cmakejs instead of gyp: cmake-js/cmake-js#205 , however static local variables are working fine, so I recommend to use those.
However the whole context aware addon problematic came to my attention just of today, so my bug might be related to my probale misunderstanding of this.
Correct me if I am wrong: Since the whole shared library is loaded only once, at the electron app seems to be just one node-process (but multiple envs/worker threads /agents), I could use per process globals like static local variables for whole process granularity, as long as I do in a thread safe matter, right? Only draw back, would be the missing clean up. But at least I could have a global USB Device context, and queue the commands issued by my JS code.
addaleax commentedon Apr 24, 2020
@Superlokkus Yes, everything you said makes sense 👍
gabrielschulhof commentedon Apr 26, 2020
@Superlokkus additionally, if you need per-addon-instance data, you can use
napi_set_instance_data()andnapi_get_instance_data()and you will get cleanup whenever an instance of your addon is unloaded. These are core N-APIs, but we're making them available in node-addon-api v3.0.0 (to be released soon) underNapi::Env::SetInstanceData()andNapi::Env::GetInstanceData().node-addon-api/napi.h
Lines 192 to 204 in 9c9accf
Whoops ... no docs yet 😳
doc: add instance data APIs
doc: add instance data APIs
doc: add instance data APIs
github-actions commentedon Dec 16, 2020
This issue is stale because it has been open many days with no activity. It will be closed soon unless the stale label is removed or a comment is made.
doc: add instance data APIs
doc: add instance data APIs
doc: add instance data APIs
doc: add instance data APIs
vipin387 commentedon Apr 9, 2024
Dear All,
This thread i think deals with some what the issue we are facing currenty.
Our requirement is that using napi addon we are trying to load core-network shared library , we are attaching callback to core-network shared library ,whenever there is change in network status , when our callback is hit ,we want to call the JS layer from the addon.
Scenario 1.
JS-Layer-------------->napi addon--->calling apis of--->network core library
Getting Result at JS<---<Return result--------------------------------return result
This is running fine for us and we are able to get the response data in JS laye
Scenario 2.
JS-Layer-------------->napi addon--->registering cabllback -- --->network core library
<????????---------callback invoked----------------------------calling callback function
Here in Scenario 2. we are facing issue in getting the napi env and calling any JS function.
Any help would be highly appreciated..
KevinEady commentedon Apr 11, 2024
Hi @vipin387 ,
This thread is a little different than what features you need. You need to use thread-safe functions.
If you need further assistance, please open a new issue.