-
Notifications
You must be signed in to change notification settings - Fork 29.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
n-api: Re-use napi_env between modules #14217
Conversation
src/node_api.cc
Outdated
@@ -705,6 +706,27 @@ bool FindWrapper(v8::Local<v8::Object> obj, | |||
return true; | |||
} | |||
|
|||
std::map<v8::Isolate*, napi_env> napi_env_map; | |||
uv_once_t napi_env_once = UV_ONCE_INIT; | |||
uv_mutex_t napi_env_mutex; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
node::Mutex
(and use node::Mutex::ScopedLock
below), then you won't need a uv_once_t either.
src/node_api.cc
Outdated
uv_mutex_lock(&napi_env_mutex); | ||
result = napi_env_map[isolate]; | ||
if (result == nullptr) { | ||
result = napi_env_map[isolate] = new napi_env__(isolate); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are napi_env instances immortal? If not, there should be code somewhere to remove it from the map again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we need a refcount then, because we need to know, for each isolate, how many modules are using the napi_env
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shall also need a v8impl::release_env()
to dereference/delete. However, these only makes sense when we have a module unload strategy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have changed the comment about v8impl::get_env()
to remind of this.
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &external, NULL, NULL)); | ||
NAPI_CALL(env, napi_get_value_external(env, external, &external_data)); | ||
NAPI_CALL(env, | ||
napi_get_boolean(env, ((napi_env)external_data) == env, &return_value)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style nit: can you line up the arguments in this file and others?
src/node_api.cc
Outdated
@@ -718,7 +740,7 @@ void napi_module_register_cb(v8::Local<v8::Object> exports, | |||
|
|||
// Create a new napi_env for this module. Once module unloading is supported | |||
// we shall have to call delete on this object from there. | |||
napi_env env = new napi_env__(context->GetIsolate()); | |||
napi_env env = v8impl::get_env(context->GetIsolate()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment should probably change now as we'd no longer delete the env when a module is unloaded.
Can you change the title for the commit comment so that Re-use is lower case -> re-use |
I think this isn't going to work when modules static-link |
@jasongin perhaps then we need a public API for per-isolate data that is better than what V8 offers. |
248260d
to
6a58919
Compare
@bnoordhuis @mhdawson I have now addressed your review comments. I'm not sure I should be implementing the As for the interoperability with statically linked node-addon-api versions mentioned by @jasongin, I think we've already broken that with the stricter wrapping, because we perform the type check based on the value of the string pointer, not its contents. |
I think that is not actually a problem, because there should never be a need for a module to unwrap an instance that was wrapped by a different module. |
@TimothyGu's idea about using TBH, if we could attach such hidden properties to the global object we'd essentially have the per- In terms of implementation, from https://github.com/nodejs/nan/blob/master/nan_private.h it looks like |
@jasongin I wouldn't dismiss the idea of cross-module wrapping/unwrapping so easily. I can imagine a scenario where we have two different packages depending on the same N-API module and the application passing objects originating from one to the other and vice versa. |
6a58919
to
f7174d0
Compare
@mhdawson @bnoordhuis @jasongin I re-wrote this patch using @TimothyGu's idea regarding |
Oh, and it takes care of the eventuality of module unloading, and the eventuality of a module having to be loaded multiple times for multiple contexts as foretold by @bnoordhuis. |
src/node_api.cc
Outdated
auto maybe_key = v8::String::NewFromUtf8(isolate, "N-API Environment", | ||
v8::NewStringType::kInternalized); | ||
CHECK(!maybe_key.IsEmpty()); | ||
auto key = v8::Private::ForApi(isolate, maybe_key.ToLocalChecked()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should try to cache the key somehow to avoid having to create a new string and getting it from the V8 internal hash map on every run. GC is a non-concern as v8::Private
s created through ForApi
will never be GC'd.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a bit of a chicken-and-egg problem. I mean, I could create a std::map
keyed on the isolate and store these v8::Private
s but then I might as well go with the previous approach of storing the napi_env
in the map's value.
This code runs once as part of DLopen()
for a given module and then never again for that module. Given this, is it worth caching this value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, storing the napi_env
instead of the v8::Private
means I lose the ability to pass a napi_env
created internally to node-addon-api, so I wouldn't revert to the previous approach. Still, I would only introduce a mutex if caching it really is worth it for the load-module use case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If module loading performance isn't a concern, I wouldn't worry about it. FWIW it's also not necessary to use a mutex as ForApi
is idempotent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought the point of caching the v8::Private
was to avoid calling ForApi
more than once per new isolate. Or did you mean that I should cache the result of v8::String::NewFromOneByte
?
If I use a std::map
I must protect it with a mutex because another thread might be in the process of depositing a new value or it might be in the process of erasing an unused value.
I suppose you mean the kind of caching that I see in node.cc, like env->exports_string()
. I guess maybe I should start looking into using node::Environment
.
src/node_api.cc
Outdated
auto isolate = context->GetIsolate(); | ||
auto global = context->Global(); | ||
|
||
auto maybe_key = v8::String::NewFromUtf8(isolate, "N-API Environment", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NewFromOneByte
would suffice.
src/node_api.cc
Outdated
auto external = v8::External::New(isolate, result); | ||
auto maybe_set = global->SetPrivate(context, key, external); | ||
CHECK(maybe_set.IsJust()); | ||
CHECK(maybe_set.FromJust()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These two checks can be combined as CHECK(maybe_set.FromJust(false))
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAICT FromJust()
doesn't take any parameters. I tried to combine, but I get
In file included from ../src/node_internals.h:28:0,
from ../src/node.h:173,
from ../src/node_buffer.h:25,
from ../src/node_api.cc:11:
../src/node_api.cc: In function ‘napi_env__* {anonymous}::v8impl::get_env(v8::Local<v8::Context>)’:
../src/node_api.cc:735:35: error: no matching function for call to ‘v8::Maybe<bool>::FromJust(bool)’
CHECK(maybe_set.FromJust(false));
^
../src/util.h:107:44: note: in definition of macro ‘UNLIKELY’
#define UNLIKELY(expr) __builtin_expect(!!(expr), 0)
^~~~
../src/node_api.cc:735:5: note: in expansion of macro ‘CHECK’
CHECK(maybe_set.FromJust(false));
^~~~~
In file included from ../src/node.h:63:0,
from ../src/node_buffer.h:25,
from ../src/node_api.cc:11:
../deps/v8/include/v8.h:8014:15: note: candidate: T v8::Maybe<T>::FromJust() const [with T = bool]
V8_INLINE T FromJust() const {
^~~~~~~~
../deps/v8/include/v8.h:8014:15: note: candidate expects 0 arguments, 1 provided
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops I meant FromMaybe(false)
c775e90
to
9ff9cf7
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Don't worry about the Private thing, not sure what I was thinking.
src/node_api.cc
Outdated
delete data.GetParameter(); | ||
} | ||
|
||
napi_env get_env(v8::Local<v8::Context> context) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All other functions in the v8impl namespace uses CamelCase. Might be good to do that here etc.
'use strict'; | ||
|
||
const common = require('../../common'); | ||
const store_env = require(`./build/${common.buildType}/store_env`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
camelCase in JS
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM modulo style nits.
src/node_api.cc
Outdated
auto global = context->Global(); | ||
|
||
auto maybe_key = v8::String::NewFromOneByte(isolate, | ||
reinterpret_cast<const uint8_t *>("N-API Environment"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: no space before *
src/node_api.cc
Outdated
|
||
auto maybe_value = global->GetPrivate(context, key); | ||
CHECK(!maybe_value.IsEmpty()); | ||
auto value = maybe_value.ToLocalChecked(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just an observation but .ToLocalChecked() also does the equivalent of CHECK(!maybe_value.IsEmpty())
.
src/node_api.cc
Outdated
auto external = v8::External::New(isolate, result); | ||
auto maybe_set = global->SetPrivate(context, key, external); | ||
CHECK(maybe_set.FromMaybe(false)); | ||
v8::Persistent<v8::External> persistent(isolate, external); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This works because ~Persistent()
currently doesn't call .Reset() but v8.h indicates that may change in the future. Not sure what to do about that but I figured you should know.
|
||
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)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
static_cast?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is C.
9ff9cf7
to
d057bfc
Compare
@TimothyGu I have updated to camelCase. @bnoordhuis I have removed the |
Store the `napi_env` on the global object at a private key. This gives us one `napi_env` per context. Re: nodejs#13872 (comment)
d057bfc
to
cdb7f13
Compare
I changed the commit message subject to contain no uppercase letters. |
If this is good to go, may I have a CI run please? |
Seems like the CI is having some issues, though. |
I think the CI is fixed now. Could somebody please submit this PR? |
Oh, NM. Thanks! |
https://ci.nodejs.org/job/node-test-pull-request/9351/ completed successfully a while ago. It shouldn't still show as pending in this PR. |
@gabrielschulhof CI status reporting is broken, don’t worry about it (not sure whether somebody’s looking into it or not). |
@addaleax NP. I'm just eager to get this landed, that's all :) |
I’m going to sleep soon but if it’s not landed tomorrow I can do that. Also, just nominated you for push & CI access, that should be a bit more practical if you like :) |
Wow! I'm honoured! Thanks! |
Landed in 9926dfe. I also amended the commit message to reference #14367 rather than #13872 (comment), as the approach we ended up taking was documented in #14367. |
Store the `napi_env` on the global object at a private key. This gives us one `napi_env` per context. Refs: #14367 PR-URL: #14217 Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Timothy Gu <[email protected]>
And also, I'd second Anna's nomination :) |
Store the `napi_env` on the global object at a private key. This gives us one `napi_env` per context. Refs: #14367 PR-URL: #14217 Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Timothy Gu <[email protected]>
Store the `napi_env` on the global object at a private key. This gives us one `napi_env` per context. Refs: nodejs#14367 PR-URL: nodejs#14217 Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Timothy Gu <[email protected]>
Store the `napi_env` on the global object at a private key. This gives us one `napi_env` per context. Refs: #14367 Backport-PR-URL: #19447 PR-URL: #14217 Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Timothy Gu <[email protected]>
Create a std::map keyed on the isolate pointer and storing one napi_env
per isolate and protect it with a mutex.
Re: #13872 (comment)
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesAffected core subsystem(s)
n-api
@bnoordhuis @mhdawson this is what sharing the
napi_env
would look like with a global static and a mutex.