Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit bb2bbc8

Browse filesBrowse files
Gabriel Schulhoftargos
authored andcommitted
n-api: add generic finalizer callback
Add `napi_add_finalizer()`, which provides the ability to attach data to an arbitrary object and be notified when that object is garbage- collected so as to have an opportunity to delete the data previously attached. This differs from `napi_wrap()` in that it does not use up the private slot on the object, and is therefore neither removable, nor retrievable after the call to `napi_add_finalizer()`. It is assumed that the data is accessible by other means, yet it must be tied to the lifetime of the object. This is the case for data passed to a dynamically created function which is itself heap-allocated and must therefore be freed along with the function. Fixes: nodejs/abi-stable-node#313 PR-URL: #22244 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
1 parent cf95b61 commit bb2bbc8
Copy full SHA for bb2bbc8

File tree

Expand file treeCollapse file tree

5 files changed

+215
-35
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

5 files changed

+215
-35
lines changed
Open diff view settings
Collapse file

‎doc/api/n-api.md‎

Copy file name to clipboardExpand all lines: doc/api/n-api.md
+58Lines changed: 58 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -3238,6 +3238,11 @@ JavaScript functions from native code. One can either call a function
32383238
like a regular JavaScript function call, or as a constructor
32393239
function.
32403240

3241+
Any non-`NULL` data which is passed to this API via the `data` field of the
3242+
`napi_property_descriptor` items can be associated with `object` and freed
3243+
whenever `object` is garbage-collected by passing both `object` and the data to
3244+
[`napi_add_finalizer`][].
3245+
32413246
### napi_call_function
32423247
<!-- YAML
32433248
added: v8.0.0
@@ -3375,6 +3380,11 @@ myaddon.sayHello();
33753380
The string passed to `require()` is the name of the target in `binding.gyp`
33763381
responsible for creating the `.node` file.
33773382

3383+
Any non-`NULL` data which is passed to this API via the `data` parameter can
3384+
be associated with the resulting JavaScript function (which is returned in the
3385+
`result` parameter) and freed whenever the function is garbage-collected by
3386+
passing both the JavaScript function and the data to [`napi_add_finalizer`][].
3387+
33783388
JavaScript `Function`s are described in
33793389
[Section 19.2](https://tc39.github.io/ecma262/#sec-function-objects)
33803390
of the ECMAScript Language Specification.
@@ -3581,6 +3591,12 @@ case, to prevent the function value from being garbage-collected, create a
35813591
persistent reference to it using [`napi_create_reference`][] and ensure the
35823592
reference count is kept >= 1.
35833593

3594+
Any non-`NULL` data which is passed to this API via the `data` parameter or via
3595+
the `data` field of the `napi_property_descriptor` array items can be associated
3596+
with the resulting JavaScript constructor (which is returned in the `result`
3597+
parameter) and freed whenever the class is garbage-collected by passing both
3598+
the JavaScript function and the data to [`napi_add_finalizer`][].
3599+
35843600
### napi_wrap
35853601
<!-- YAML
35863602
added: v8.0.0
@@ -3685,6 +3701,47 @@ object `js_object` using `napi_wrap()` and removes the wrapping. If a finalize
36853701
callback was associated with the wrapping, it will no longer be called when the
36863702
JavaScript object becomes garbage-collected.
36873703

3704+
### napi_add_finalizer
3705+
<!-- YAML
3706+
added: v8.0.0
3707+
napiVersion: 1
3708+
-->
3709+
```C
3710+
napi_status napi_add_finalizer(napi_env env,
3711+
napi_value js_object,
3712+
void* native_object,
3713+
napi_finalize finalize_cb,
3714+
void* finalize_hint,
3715+
napi_ref* result);
3716+
```
3717+
3718+
- `[in] env`: The environment that the API is invoked under.
3719+
- `[in] js_object`: The JavaScript object to which the native data will be
3720+
attached.
3721+
- `[in] native_object`: The native data that will be attached to the JavaScript
3722+
object.
3723+
- `[in] finalize_cb`: Native callback that will be used to free the
3724+
native data when the JavaScript object is ready for garbage-collection.
3725+
- `[in] finalize_hint`: Optional contextual hint that is passed to the
3726+
finalize callback.
3727+
- `[out] result`: Optional reference to the JavaScript object.
3728+
3729+
Returns `napi_ok` if the API succeeded.
3730+
3731+
Adds a `napi_finalize` callback which will be called when the JavaScript object
3732+
in `js_object` is ready for garbage collection. This API is similar to
3733+
`napi_wrap()` except that
3734+
* the native data cannot be retrieved later using `napi_unwrap()`,
3735+
* nor can it be removed later using `napi_remove_wrap()`, and
3736+
* the API can be called multiple times with different data items in order to
3737+
attach each of them to the JavaScript object.
3738+
3739+
*Caution*: The optional returned reference (if obtained) should be deleted via
3740+
[`napi_delete_reference`][] ONLY in response to the finalize callback
3741+
invocation. If it is deleted before then, then the finalize callback may never
3742+
be invoked. Therefore, when obtaining a reference a finalize callback is also
3743+
required in order to enable correct disposal of the reference.
3744+
36883745
## Simple Asynchronous Operations
36893746

36903747
Addon modules often need to leverage async helpers from libuv as part of their
@@ -4559,6 +4616,7 @@ This API may only be called from the main thread.
45594616
[Working with JavaScript Values]: #n_api_working_with_javascript_values
45604617
[Working with JavaScript Values - Abstract Operations]: #n_api_working_with_javascript_values_abstract_operations
45614618

4619+
[`napi_add_finalizer`]: #n_api_napi_add_finalizer
45624620
[`napi_async_init`]: #n_api_napi_async_init
45634621
[`napi_cancel_async_work`]: #n_api_napi_cancel_async_work
45644622
[`napi_close_escapable_handle_scope`]: #n_api_napi_close_escapable_handle_scope
Collapse file

‎src/node_api.cc‎

Copy file name to clipboardExpand all lines: src/node_api.cc
+77-35Lines changed: 77 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1157,6 +1157,63 @@ class ThreadSafeFunction : public node::AsyncResource {
11571157
bool handles_closing;
11581158
};
11591159

1160+
enum WrapType {
1161+
retrievable,
1162+
anonymous
1163+
};
1164+
1165+
template <WrapType wrap_type> static inline
1166+
napi_status Wrap(napi_env env,
1167+
napi_value js_object,
1168+
void* native_object,
1169+
napi_finalize finalize_cb,
1170+
void* finalize_hint,
1171+
napi_ref* result) {
1172+
NAPI_PREAMBLE(env);
1173+
CHECK_ARG(env, js_object);
1174+
1175+
v8::Isolate* isolate = env->isolate;
1176+
v8::Local<v8::Context> context = isolate->GetCurrentContext();
1177+
1178+
v8::Local<v8::Value> value = v8impl::V8LocalValueFromJsValue(js_object);
1179+
RETURN_STATUS_IF_FALSE(env, value->IsObject(), napi_invalid_arg);
1180+
v8::Local<v8::Object> obj = value.As<v8::Object>();
1181+
1182+
if (wrap_type == retrievable) {
1183+
// If we've already wrapped this object, we error out.
1184+
RETURN_STATUS_IF_FALSE(env,
1185+
!obj->HasPrivate(context, NAPI_PRIVATE_KEY(context, wrapper))
1186+
.FromJust(),
1187+
napi_invalid_arg);
1188+
} else if (wrap_type == anonymous) {
1189+
// If no finalize callback is provided, we error out.
1190+
CHECK_ARG(env, finalize_cb);
1191+
}
1192+
1193+
v8impl::Reference* reference = nullptr;
1194+
if (result != nullptr) {
1195+
// The returned reference should be deleted via napi_delete_reference()
1196+
// ONLY in response to the finalize callback invocation. (If it is deleted
1197+
// before then, then the finalize callback will never be invoked.)
1198+
// Therefore a finalize callback is required when returning a reference.
1199+
CHECK_ARG(env, finalize_cb);
1200+
reference = v8impl::Reference::New(
1201+
env, obj, 0, false, finalize_cb, native_object, finalize_hint);
1202+
*result = reinterpret_cast<napi_ref>(reference);
1203+
} else {
1204+
// Create a self-deleting reference.
1205+
reference = v8impl::Reference::New(env, obj, 0, true, finalize_cb,
1206+
native_object, finalize_cb == nullptr ? nullptr : finalize_hint);
1207+
}
1208+
1209+
if (wrap_type == retrievable) {
1210+
CHECK(obj->SetPrivate(context, NAPI_PRIVATE_KEY(context, wrapper),
1211+
v8::External::New(isolate, reference)).FromJust());
1212+
}
1213+
1214+
return GET_RETURN_STATUS(env);
1215+
}
1216+
11601217
} // end of namespace v8impl
11611218

11621219
// Intercepts the Node-V8 module registration callback. Converts parameters
@@ -2859,41 +2916,12 @@ napi_status napi_wrap(napi_env env,
28592916
napi_finalize finalize_cb,
28602917
void* finalize_hint,
28612918
napi_ref* result) {
2862-
NAPI_PREAMBLE(env);
2863-
CHECK_ARG(env, js_object);
2864-
2865-
v8::Isolate* isolate = env->isolate;
2866-
v8::Local<v8::Context> context = isolate->GetCurrentContext();
2867-
2868-
v8::Local<v8::Value> value = v8impl::V8LocalValueFromJsValue(js_object);
2869-
RETURN_STATUS_IF_FALSE(env, value->IsObject(), napi_invalid_arg);
2870-
v8::Local<v8::Object> obj = value.As<v8::Object>();
2871-
2872-
// If we've already wrapped this object, we error out.
2873-
RETURN_STATUS_IF_FALSE(env,
2874-
!obj->HasPrivate(context, NAPI_PRIVATE_KEY(context, wrapper)).FromJust(),
2875-
napi_invalid_arg);
2876-
2877-
v8impl::Reference* reference = nullptr;
2878-
if (result != nullptr) {
2879-
// The returned reference should be deleted via napi_delete_reference()
2880-
// ONLY in response to the finalize callback invocation. (If it is deleted
2881-
// before then, then the finalize callback will never be invoked.)
2882-
// Therefore a finalize callback is required when returning a reference.
2883-
CHECK_ARG(env, finalize_cb);
2884-
reference = v8impl::Reference::New(
2885-
env, obj, 0, false, finalize_cb, native_object, finalize_hint);
2886-
*result = reinterpret_cast<napi_ref>(reference);
2887-
} else {
2888-
// Create a self-deleting reference.
2889-
reference = v8impl::Reference::New(env, obj, 0, true, finalize_cb,
2890-
native_object, finalize_cb == nullptr ? nullptr : finalize_hint);
2891-
}
2892-
2893-
CHECK(obj->SetPrivate(context, NAPI_PRIVATE_KEY(context, wrapper),
2894-
v8::External::New(isolate, reference)).FromJust());
2895-
2896-
return GET_RETURN_STATUS(env);
2919+
return v8impl::Wrap<v8impl::retrievable>(env,
2920+
js_object,
2921+
native_object,
2922+
finalize_cb,
2923+
finalize_hint,
2924+
result);
28972925
}
28982926

28992927
napi_status napi_unwrap(napi_env env, napi_value obj, void** result) {
@@ -4138,3 +4166,17 @@ napi_ref_threadsafe_function(napi_env env, napi_threadsafe_function func) {
41384166
CHECK(func != nullptr);
41394167
return reinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Ref();
41404168
}
4169+
4170+
napi_status napi_add_finalizer(napi_env env,
4171+
napi_value js_object,
4172+
void* native_object,
4173+
napi_finalize finalize_cb,
4174+
void* finalize_hint,
4175+
napi_ref* result) {
4176+
return v8impl::Wrap<v8impl::anonymous>(env,
4177+
js_object,
4178+
native_object,
4179+
finalize_cb,
4180+
finalize_hint,
4181+
result);
4182+
}
Collapse file

‎src/node_api.h‎

Copy file name to clipboardExpand all lines: src/node_api.h
+6Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,12 @@ NAPI_EXTERN napi_status napi_get_value_bigint_words(napi_env env,
695695
int* sign_bit,
696696
size_t* word_count,
697697
uint64_t* words);
698+
NAPI_EXTERN napi_status napi_add_finalizer(napi_env env,
699+
napi_value js_object,
700+
void* native_object,
701+
napi_finalize finalize_cb,
702+
void* finalize_hint,
703+
napi_ref* result);
698704
#endif // NAPI_EXPERIMENTAL
699705

700706
EXTERN_C_END
Collapse file
+33Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
'use strict';
2+
// Flags: --expose-gc
3+
4+
const common = require('../../common');
5+
const test_general = require(`./build/${common.buildType}/test_general`);
6+
const assert = require('assert');
7+
8+
let finalized = {};
9+
const callback = common.mustCall(2);
10+
11+
// Add two items to be finalized and ensure the callback is called for each.
12+
test_general.addFinalizerOnly(finalized, callback);
13+
test_general.addFinalizerOnly(finalized, callback);
14+
15+
// Ensure attached items cannot be retrieved.
16+
common.expectsError(() => test_general.unwrap(finalized),
17+
{ type: Error, message: 'Invalid argument' });
18+
19+
// Ensure attached items cannot be removed.
20+
common.expectsError(() => test_general.removeWrap(finalized),
21+
{ type: Error, message: 'Invalid argument' });
22+
finalized = null;
23+
global.gc();
24+
25+
// Add an item to an object that is already wrapped, and ensure that its
26+
// finalizer as well as the wrap finalizer gets called.
27+
let finalizeAndWrap = {};
28+
test_general.wrap(finalizeAndWrap);
29+
test_general.addFinalizerOnly(finalizeAndWrap, common.mustCall());
30+
finalizeAndWrap = null;
31+
global.gc();
32+
assert.strictEqual(test_general.derefItemWasCalled(), true,
33+
'finalize callback was called');
Collapse file

‎test/addons-napi/test_general/test_general.c‎

Copy file name to clipboardExpand all lines: test/addons-napi/test_general/test_general.c
+41Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#define NAPI_EXPERIMENTAL
12
#include <node_api.h>
23
#include <stdlib.h>
34
#include "../common.h"
@@ -177,6 +178,17 @@ static napi_value wrap(napi_env env, napi_callback_info info) {
177178
return NULL;
178179
}
179180

181+
static napi_value unwrap(napi_env env, napi_callback_info info) {
182+
size_t argc = 1;
183+
napi_value wrapped;
184+
void* data;
185+
186+
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &wrapped, NULL, NULL));
187+
NAPI_CALL(env, napi_unwrap(env, wrapped, &data));
188+
189+
return NULL;
190+
}
191+
180192
static napi_value remove_wrap(napi_env env, napi_callback_info info) {
181193
size_t argc = 1;
182194
napi_value wrapped;
@@ -232,6 +244,33 @@ static napi_value testNapiRun(napi_env env, napi_callback_info info) {
232244
return result;
233245
}
234246

247+
static void finalizer_only_callback(napi_env env, void* data, void* hint) {
248+
napi_ref js_cb_ref = data;
249+
napi_value js_cb, undefined;
250+
NAPI_CALL_RETURN_VOID(env, napi_get_reference_value(env, js_cb_ref, &js_cb));
251+
NAPI_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined));
252+
NAPI_CALL_RETURN_VOID(env,
253+
napi_call_function(env, undefined, js_cb, 0, NULL, NULL));
254+
NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, js_cb_ref));
255+
}
256+
257+
static napi_value add_finalizer_only(napi_env env, napi_callback_info info) {
258+
size_t argc = 2;
259+
napi_value argv[2];
260+
napi_ref js_cb_ref;
261+
262+
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
263+
NAPI_CALL(env, napi_create_reference(env, argv[1], 1, &js_cb_ref));
264+
NAPI_CALL(env,
265+
napi_add_finalizer(env,
266+
argv[0],
267+
js_cb_ref,
268+
finalizer_only_callback,
269+
NULL,
270+
NULL));
271+
return NULL;
272+
}
273+
235274
static napi_value Init(napi_env env, napi_value exports) {
236275
napi_property_descriptor descriptors[] = {
237276
DECLARE_NAPI_PROPERTY("testStrictEquals", testStrictEquals),
@@ -246,7 +285,9 @@ static napi_value Init(napi_env env, napi_value exports) {
246285
DECLARE_NAPI_PROPERTY("testNapiErrorCleanup", testNapiErrorCleanup),
247286
DECLARE_NAPI_PROPERTY("testNapiTypeof", testNapiTypeof),
248287
DECLARE_NAPI_PROPERTY("wrap", wrap),
288+
DECLARE_NAPI_PROPERTY("unwrap", unwrap),
249289
DECLARE_NAPI_PROPERTY("removeWrap", remove_wrap),
290+
DECLARE_NAPI_PROPERTY("addFinalizerOnly", add_finalizer_only),
250291
DECLARE_NAPI_PROPERTY("testFinalizeWrap", test_finalize_wrap),
251292
DECLARE_NAPI_PROPERTY("finalizeWasCalled", finalize_was_called),
252293
DECLARE_NAPI_PROPERTY("derefItemWasCalled", deref_item_was_called),

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.