-
Notifications
You must be signed in to change notification settings - Fork 30k
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
node-api: enable napi_ref for all value types #42557
Conversation
Review requested:
|
This PR was discussed today in the Node-API meeting and the suggestion was to reuse the existing
The new functions are going to have the same name as the current function, but to have prefix |
Adding "blocked" label as #42557 (comment) said to prevent unexpected landing. Feel free to remove the label when it is not the case anymore. |
Hi @nodejs/node-api , can we get a new review after @vmoroz 's implementation changes that address the blocked issue re. #42557 (comment) ? Thanks |
516e36e
to
efd23fd
Compare
doc/api/n-api.md
Outdated
|
||
<!-- YAML | ||
added: REPLACEME | ||
--> |
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.
Doc should indicated as experimental as well.
We discussed in the 20 May Node-API meeting that @vmoroz would like to change the enum names for |
3fdb722
to
4f8cbed
Compare
@mhdawson, @legendecas, @NickNaso, per our discussion today at the Node-API meeting, I had updated the PR description and added a note to the doc that |
f43b537
to
b320716
Compare
Some thoughts on naming. Maybe these would work?
Where we can explain the difference being that
In addition to the naming are we sure that all JavaScript engines will support making persistent references to all types or have what's needed for the node-api implementation for the engine to be able implement something that looks like a reference? |
AFAICT, the main blocker for supporting arbitrary values in However, node-api implementation of As the weak reference is not guaranteed to be consistent, as those values can be collected anytime, I believe the support of create strong references on primitive values can meet the criteria New API should be agnostic towards the underlying JavaScript VM and "A new API addition should be simultaneously implemented in at least one other VM implementation of Node.js". I believe it would be beneficial to strive to extend the maximum capability of existing APIs and try not to introduce new APIs for differences that are hard for end-users to choose between. |
@legendecas, do you think that instead of adding new API we should just change the implementation of the existing references to support all value types and just say in the docs that the weak references work only object-like values? |
ba396aa
to
bffedbb
Compare
@mhdawson, I have changed the names. |
Yes, we should try to extend the current API. For documentation, we should not distinguish types but instead say that the behavior of the weak reference is determined by the engine implementation, and is not guaranteed to be able to get the referenced value when the ref-count is reached 0. |
bffedbb
to
b39f201
Compare
b39f201
to
70a98de
Compare
Hi @nodejs/node-api , This PR introduces two changes: (1) allowing references to all values and (2) the ability for modules to request certain feature sets at module registration. @vmoroz has requested some feedback on both of these points if possible. |
70a98de
to
6b1f36d
Compare
@mhdawson, @legendecas, I have updated the PR - please review. |
src/node_api.h
Outdated
@@ -38,7 +38,12 @@ typedef struct napi_module { | |||
napi_addon_register_func nm_register_func; | |||
const char* nm_modname; | |||
void* nm_priv; | |||
#ifdef NAPI_EXPERIMENTAL | |||
napi_features* nm_features; |
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 can be a good start for node-api to get started with backward-compatible feature additions!
As you mentioned in the node-api meetings, this bit flag (an enum is an int size) may only represent 32 features at most. I'm wondering if it would be more extensible to save the module's defined NAPI_VERSION
as nm_napi_version
here like nm_version
instead. In this way, node can refuse to load a node-api addon when an addon requires NAPI_VERSION 10, but the node is compiled as NAPI_VERSION 9.
The drawback of the alternative is that people have to pick up all feature changes with the new NAPI_VERSION, not part of it. But this could also be a relief that we don't need to maintain a long list of features, but a version support list instead.
What do you think?
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 like the idea of passing Node-API version used for a module. This way we can enforce the version compatibility.
The only drawback could be that developers will not be able to choose functionality depending on the Node-API version at runtime. I am not sure if anyone does it though.
As for the 32-bit feature set limit, the proposal is to pass not the feature bits to the module, but rather the pointer to the feature set. This way we can use the first 31 bits normally. In case if we need more, then we can set the 32nd bit and it will mean that the feature set has the second 32bit number, and the feature set pointer becomes a pointer to the feature set array with two elements. We can extend this array to be as long as needed. Obviously, each new entry in this array will require its own enum type. In practice I doubt that we ever exceed the 31 bit limit, but if we do, then we can extend it using this approach.
src/js_native_api_v8.cc
Outdated
@@ -576,7 +576,9 @@ Reference::Reference(napi_env env, v8::Local<v8::Value> value, Args&&... args) | |||
: RefBase(env, std::forward<Args>(args)...), | |||
_persistent(env->isolate, value), | |||
_secondPassParameter(new SecondPassCallParameterRef(this)), | |||
_secondPassScheduled(false) { | |||
_secondPassScheduled(false), | |||
_canBeWeak(!env->IsFeatureEnabled(napi_feature_reference_all_types) || |
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 never allowed creating a reference on primitive values (except Symbol
s). So I believe there is no need to check the feature bit here: reference on primitive values can not be a weak reference.
_canBeWeak(!env->IsFeatureEnabled(napi_feature_reference_all_types) || | |
_canBeWeak( |
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 intent here is to stop offering weak references for Symbols with the new flag and only do it for Objects and Functions to better match the JavaScript spec. But since currently we support weak references for Symbols and have unit tests in napi_references
for them, I had to put the flag check here. This way the old code works without changes if the flag is not set.
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 removed the _canBeWeak
field completely to let V8 engine to decide on the weak references' behavior.
The only true weak references, as it used to be before, are object, external objects, and functions.
All other types are strong references when the ref count is 0.
This behavior can be seen in the new tests.
The only difference between what we used to have before and now is that we allow all types instead of only four (object, external object, function, and symbol) to be ref counted.
It almost feels like that allowing use of napi_ref
for all value types may not need a special feature, but we should rather just extend the existing behavior as we did previously for Symbols.
f8bbe4c
to
e629646
Compare
node_api_default_experimental_features = node_api_feature_reference_all_types, | ||
|
||
// version specific | ||
node_api_default_features = node_api_default_experimental_features, |
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.
For compatiblity I'm wondering if we should tie the defaults to a Node-API version versus the vesion of Node.js Unless the module author choses to opt-in, they behaviour should not change unless they have moved up to a newer Node-API vesion?
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.
Looking more closely I think that is the approach that is described, but but nothing is guarded because its under experimental. Will make a comment in src/js_native_api_types.h
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.
My intent is to make it per Node-API version.
// Not only objects, external objects, functions, and symbols as before. | ||
node_api_feature_reference_all_types = 1 << 0, | ||
// Each version of Node-API is going to have its own default set of features. | ||
node_api_default_experimental_features = node_api_feature_reference_all_types, |
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'm not sure if we should have this, I think we should guard the availabilty to be able to turn on experimental features with NAPI_EXPERIMENTAL, but I'm not sure if having a set different than the default for the Node-API version makes sense. Otherwise somebody who wants to use any experimental API is also forced into to new defaults.
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.
In case if someone does not want to use the new experimental default, they can always override it per module.
doc/api/n-api.md
Outdated
``` | ||
|
||
* `[in] env`: The environment that the API is invoked under. | ||
* `[in] feature`: The feature that we want to test. |
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.
* `[in] feature`: The feature that we want to test. | |
* `[in] feature`: The features that we want to test. |
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.
Changed. Though I am not sure why we need to use plural form, while the parameter is singular. Should I also rename the parameter?
@@ -3257,3 +3259,12 @@ napi_status NAPI_CDECL napi_is_detached_arraybuffer(napi_env env, | |||
|
|||
return napi_clear_last_error(env); | |||
} | |||
|
|||
napi_status NAPI_CDECL node_api_is_feature_enabled(napi_env 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.
I wonder if this should be in node_api.cc instead of this file. It is not tied to v8 right?
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.
It is not tied to V8. I was thinking that we want to have features available for different JS engines.
#else // NODE_API_CUSTOM_FEATURES | ||
|
||
#define NODE_API_DEFINE_DEFAULT_FEATURES \ | ||
static node_api_features node_api_module_features = node_api_default_features; |
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.
Comments as before, I think this will end up setting to be specific to the Node.js version used to build versus the Node-API version selected. Should discuss this more.
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 am not sure how to address this. Let's discuss it in the Node-API meeting. The concern as I understand it is that if we add more experimental features, we may fail to propagate them to the previous versions of Node.JS, and thus it may cause differences in the behavior between Node.JS versions, while we have the same version of Node-API, right?
#define NODE_API_DEFINE_DEFAULT_FEATURES | ||
#define NODE_API_FEATURES_PTR | ||
#endif // NAPI_EXPERIMENTAL | ||
|
||
#define NAPI_MODULE_X(modname, regfunc, priv, flags) \ |
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.
Should we just create a new NAPI_MODULE_X_FEATURES which takes the features parameter?
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.
It may be simpler - let me try.
The issue
In the Node-API the
napi_value
exists only on the call stack. When the value needs to be persisted after the call stack is unwinded, we can use thenapi_ref
. Currently thenapi_ref
keepsnapi_value
only fornapi_object
,napi_function
,napi_external
, andnapi_symbol
types becausenapi_ref
must support weak pointer semantic. Thus, there was a decision to not keep persistent values for other types such asnapi_string
ornapi_number
. Though, thenapi_symbol
cannot have the weak semantic, but we still can have anapi_ref
for it.Solution
In this PR we enable use of
napi_ref
for strong references to allnapi_value
types. Though, only references fornapi_object
,napi_external
, andnapi_function
types could have the true weak reference semantic. These three types could be collected by GC when the ref count is 0, while other types cannot be collected because they are always string references. To keep the backward compatibility, this PR enables the new behavior under a specialnode_api_features
bit flagnode_api_feature_reference_all_types
. Thenode_api_features
are being introduced in this PR.The new
node_api_features
allow changing internal behavior of existing Node-API functions.We pass a
node_api_features
pointer to thenapi_module
struct in theNAPI_MODULE_X
macro. This macro is used for the module registration. If the module is initialized without using this macro, then there will be no features selected and the module will use thenode_api_feature_none
.Each Node-API version is going to define its own default set of features. For the current version it can be accessed using
node_api_default_features
. A module can override the set of its enabled features by addingNODE_API_CUSTOM_FEATURES
definition to the.gyp
file and then setting the value of the globalnode_api_module_features
variable. To check enabled features, use thenode_api_is_feature_enabled
function.For example, to disable
node_api_feature_reference_all_types
we can exclude its bit from thenode_api_default_features
:Documentation
The
n-api.md
documentation is updated with the info aboutnode_api_features
enum and thenode_api_is_feature_enabled
function.Testing
Added three new tests:
js-native-api/test_reference_all_types
.The test stores
napi_value
of different types and then retrieves them for the strong and weaknapi_ref
references.node-api/test_init_reference_all_types
. The same as the previous test, but it usesNAPI_MODULE_INIT
macro to initialize module instead ofNAPI_MODULE
. This is to verify that bothNAPI_MODULE
andNAPI_MODULE_INIT
macro support feature specification.node-api/test_init_reference_obj_only
. To test old references behavior and module initialization withNAPI_MODULE_INIT
macro.The
test_reference
andtest_init_reference_obj_only
disable thenode_api_feature_reference_all_types
feature and make sure that the oldnapi_ref
behavior continues to work.The
NAPI_EXPERIMENTAL
is added tocommon.h
andentry_point.c
intest/js-native-api
folder to make sure that thejs-native-api
tests always use the latest Node-API version features.