Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
0718780
fs: implement mkdir recursive (mkdirp)
Aug 9, 2018
08411d8
test: add comment describing test-fs-mkdir
Aug 21, 2018
e22b0ed
url: provide pathToFileURL and fileURLToPath
guybedford Aug 24, 2018
8d64c40
test: check parameter type of fs.mkdir()
Aug 30, 2018
94b662a
test: refactor structure of common/index
jasnell Aug 24, 2018
52877ee
assert: align argument names
BridgeAR Sep 8, 2018
58d0833
doc: add history for withFileTypes in fs.readdir[Sync]()
tiendq Sep 10, 2018
1b6487c
crypto: fix public key encryption internals
tniessen Sep 9, 2018
350ef49
crypto: rename symbols to match guidelines
tniessen Sep 5, 2018
684705c
doc: add gabrielschulhof to TSC
Trott Sep 12, 2018
e0bc988
n-api: add generic finalizer callback
Aug 10, 2018
6e5cd4d
test: checks on napi factory wrap’s finalization
legendecas Aug 31, 2018
17733d3
lib: remove unnecessary symbols
Aug 22, 2018
00a8511
test: minor refactor in common/index.js
jasnell Sep 6, 2018
873ebeb
doc: document http2 timeouts
sagitsofan Sep 10, 2018
c8c922a
doc: add reference to guide for N-API additions
mhdawson Aug 29, 2018
3ec5a64
deps: cherry-pick 2363cdf from upstream V8
ofrobots Sep 11, 2018
ff60980
trace_events: avoid flusing uninitialized traces
ofrobots Sep 12, 2018
e9b5c34
fs: ensure readdir() callback is only called once
cjihrig Sep 10, 2018
2550015
lib: simplify 'processChunkSync'
Sep 11, 2018
ca0a92f
module: add createRequireFunction method
devsnek Mar 14, 2018
217fb2e
worker: correct (de)initialization order
addaleax Sep 9, 2018
e5d67f7
tools: implement update-authors in JS
addaleax Sep 9, 2018
e52ea8b
doc: update AUTHORS list
addaleax Sep 9, 2018
e268b64
src: move getActiveResources/Handles to node_process.cc
jasnell Sep 7, 2018
415eac8
src: move DebugPortGetter/Setter to node_process.cc
jasnell Sep 7, 2018
536c6ed
doc: fix typo in dns docs
MohammedEssehemy Sep 14, 2018
14d1b7a
doc: add withFileTypes option to fsPromises.readdir
bengl Sep 13, 2018
cfced0c
process: generate list of allowed env flags programmatically
addaleax Aug 31, 2018
60dc374
lib: generate allowedNodeEnvironmentFlags lazily
addaleax Aug 31, 2018
d723cbd
doc: add full deprecation history
tniessen Sep 8, 2018
1cf6e3e
fs: fix promisified fs.readdir withFileTypes
apapirovski Sep 13, 2018
466ed2e
src: fix `--prof-process` CLI argument handling
addaleax Sep 10, 2018
572d01c
tracing: remove shutdown-on-signal
addaleax Sep 6, 2018
2f5fad0
path: remove unnecessary if statement
wchargin Aug 11, 2018
75422ab
tools: update ESLint to 5.6.0
Trott Sep 16, 2018
3120dab
src: move no_async_hooks_checks to env
danbev Sep 10, 2018
dfef9a9
doc: add missing options for crypto sign.sign()
mbj36 Sep 12, 2018
5cb642e
assert: add default operator to `assert.fail()`
BridgeAR Sep 4, 2018
1433653
http2: add http2stream.endAfterHeaders property
jasnell Sep 13, 2018
56b17d2
doc: improve asymmetric crypto docs
addaleax Sep 12, 2018
721508d
src: refactor `Environment::GetCurrent()` usage
addaleax Sep 12, 2018
2f88373
build: skip cctest on Windows shared lib build
yhwang Jul 24, 2018
c17ba24
string_decoder: support typed array or data view
BeniCheni Aug 28, 2018
2a3dea5
doc: update 6.x to 8.x in backporting wiki
trivikr Sep 15, 2018
325b82a
inspector: enable Inspector JS API in workers
Sep 9, 2018
9ca9e81
errors: add useOriginalName to internal/errors
joyeecheung Aug 27, 2018
e07e573
build: do not lint fixtures in make lint-md
joyeecheung Aug 27, 2018
97f6ff3
doc: add boneskull as collaborator
boneskull Sep 17, 2018
97979b7
tools,win: fix find_python error
kfarnung Sep 10, 2018
85fca5d
tools: merge custom cpplint with cpplint v1.3.0
boneskull Sep 13, 2018
6f66e49
deps: add missing HandleScope in FieldType::PrintTo
hashseed Sep 17, 2018
2b0ce98
crypto: remove unused scrypt validation parameter
tniessen Sep 17, 2018
6975e08
crypto: fix edge case in authenticated encryption
tniessen Sep 12, 2018
57f2c12
test: don't inspect values if not necessary
BridgeAR Sep 17, 2018
bf7d7cf
test: remove string literal message from assertion
Trott Sep 13, 2018
8595d90
test: remove string literal message in assertions
Trott Sep 13, 2018
b9af09d
test: improve assertion in test-inspector.js
Trott Sep 2, 2018
05bec3c
test: simplify assertion in http2 tests
Trott Sep 2, 2018
f04466e
test: refactor flag check
Trott Sep 13, 2018
4fe6034
test: remove string literal from assertion
Trott Sep 13, 2018
136c4a8
test: remove string literal message from assertion
Trott Sep 13, 2018
f2703b2
test: remove string literal arg from assertion
Trott Sep 13, 2018
bd24752
test: remove string literal from assertion
Trott Sep 13, 2018
6f23ec6
test: remove string literal from assertion
Trott Sep 13, 2018
3bf9c17
test: prepare test-assert for strictEqual linting
Trott Sep 13, 2018
0fd614d
tools: prevent string literals in some assertions
Trott Jul 30, 2018
6f02cc5
deps: cherry-pick dbfcc48 from upstream V8
alexkozy Aug 10, 2018
ce58979
src: added URL::FromFilePath method
alexkozy Aug 30, 2018
7f985da
inspector: implemented V8InspectorClient::resourceNameToUrl
alexkozy Aug 30, 2018
f297866
deps: cherry-pick 9a23bdd from upstream V8
Drieger Sep 17, 2018
c68addd
lib,doc: remove unused parameter, improve docs
Sep 14, 2018
89439ac
tools: synchronize deepStrictEqual() message rules
Trott Sep 17, 2018
d46ce65
doc: explain how to invoke gc
isurusiri Apr 30, 2018
d4278a0
doc, win: improve os.setPriority documentation
bzoz Sep 12, 2018
7107451
worker: only stop inspector if started
addaleax Sep 18, 2018
e6250f4
inspector: workers debugging
Sep 9, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
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>
  • Loading branch information
Gabriel Schulhof authored and targos committed Sep 19, 2018
commit e0bc9882e3f8bbb7d3efb041c48a97c752a7dfe6
58 changes: 58 additions & 0 deletions doc/api/n-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -3238,6 +3238,11 @@ JavaScript functions from native code. One can either call a function
like a regular JavaScript function call, or as a constructor
function.

Any non-`NULL` data which is passed to this API via the `data` field of the
`napi_property_descriptor` items can be associated with `object` and freed
whenever `object` is garbage-collected by passing both `object` and the data to
[`napi_add_finalizer`][].

### napi_call_function
<!-- YAML
added: v8.0.0
Expand Down Expand Up @@ -3375,6 +3380,11 @@ myaddon.sayHello();
The string passed to `require()` is the name of the target in `binding.gyp`
responsible for creating the `.node` file.

Any non-`NULL` data which is passed to this API via the `data` parameter can
be associated with the resulting JavaScript function (which is returned in the
`result` parameter) and freed whenever the function is garbage-collected by
passing both the JavaScript function and the data to [`napi_add_finalizer`][].

JavaScript `Function`s are described in
[Section 19.2](https://tc39.github.io/ecma262/#sec-function-objects)
of the ECMAScript Language Specification.
Expand Down Expand Up @@ -3581,6 +3591,12 @@ case, to prevent the function value from being garbage-collected, create a
persistent reference to it using [`napi_create_reference`][] and ensure the
reference count is kept >= 1.

Any non-`NULL` data which is passed to this API via the `data` parameter or via
the `data` field of the `napi_property_descriptor` array items can be associated
with the resulting JavaScript constructor (which is returned in the `result`
parameter) and freed whenever the class is garbage-collected by passing both
the JavaScript function and the data to [`napi_add_finalizer`][].

### napi_wrap
<!-- YAML
added: v8.0.0
Expand Down Expand Up @@ -3685,6 +3701,47 @@ object `js_object` using `napi_wrap()` and removes the wrapping. If a finalize
callback was associated with the wrapping, it will no longer be called when the
JavaScript object becomes garbage-collected.

### napi_add_finalizer
<!-- YAML
added: v8.0.0
napiVersion: 1
-->
```C
napi_status napi_add_finalizer(napi_env env,
napi_value js_object,
void* native_object,
napi_finalize finalize_cb,
void* finalize_hint,
napi_ref* result);
```

- `[in] env`: The environment that the API is invoked under.
- `[in] js_object`: The JavaScript object to which the native data will be
attached.
- `[in] native_object`: The native data that will be attached to the JavaScript
object.
- `[in] finalize_cb`: Native callback that will be used to free the
native data when the JavaScript object is ready for garbage-collection.
- `[in] finalize_hint`: Optional contextual hint that is passed to the
finalize callback.
- `[out] result`: Optional reference to the JavaScript object.

Returns `napi_ok` if the API succeeded.

Adds a `napi_finalize` callback which will be called when the JavaScript object
in `js_object` is ready for garbage collection. This API is similar to
`napi_wrap()` except that
* the native data cannot be retrieved later using `napi_unwrap()`,
* nor can it be removed later using `napi_remove_wrap()`, and
* the API can be called multiple times with different data items in order to
attach each of them to the JavaScript object.

*Caution*: The optional returned reference (if obtained) should be deleted via
[`napi_delete_reference`][] ONLY in response to the finalize callback
invocation. If it is deleted before then, then the finalize callback may never
be invoked. Therefore, when obtaining a reference a finalize callback is also
required in order to enable correct disposal of the reference.

## Simple Asynchronous Operations

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

[`napi_add_finalizer`]: #n_api_napi_add_finalizer
[`napi_async_init`]: #n_api_napi_async_init
[`napi_cancel_async_work`]: #n_api_napi_cancel_async_work
[`napi_close_escapable_handle_scope`]: #n_api_napi_close_escapable_handle_scope
Expand Down
112 changes: 77 additions & 35 deletions src/node_api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1157,6 +1157,63 @@ class ThreadSafeFunction : public node::AsyncResource {
bool handles_closing;
};

enum WrapType {
retrievable,
anonymous
};

template <WrapType wrap_type> static inline
napi_status Wrap(napi_env env,
napi_value js_object,
void* native_object,
napi_finalize finalize_cb,
void* finalize_hint,
napi_ref* result) {
NAPI_PREAMBLE(env);
CHECK_ARG(env, js_object);

v8::Isolate* isolate = env->isolate;
v8::Local<v8::Context> context = isolate->GetCurrentContext();

v8::Local<v8::Value> value = v8impl::V8LocalValueFromJsValue(js_object);
RETURN_STATUS_IF_FALSE(env, value->IsObject(), napi_invalid_arg);
v8::Local<v8::Object> obj = value.As<v8::Object>();

if (wrap_type == retrievable) {
// If we've already wrapped this object, we error out.
RETURN_STATUS_IF_FALSE(env,
!obj->HasPrivate(context, NAPI_PRIVATE_KEY(context, wrapper))
.FromJust(),
napi_invalid_arg);
} else if (wrap_type == anonymous) {
// If no finalize callback is provided, we error out.
CHECK_ARG(env, finalize_cb);
}

v8impl::Reference* reference = nullptr;
if (result != nullptr) {
// The returned reference should be deleted via napi_delete_reference()
// ONLY in response to the finalize callback invocation. (If it is deleted
// before then, then the finalize callback will never be invoked.)
// Therefore a finalize callback is required when returning a reference.
CHECK_ARG(env, finalize_cb);
reference = v8impl::Reference::New(
env, obj, 0, false, finalize_cb, native_object, finalize_hint);
*result = reinterpret_cast<napi_ref>(reference);
} else {
// Create a self-deleting reference.
reference = v8impl::Reference::New(env, obj, 0, true, finalize_cb,
native_object, finalize_cb == nullptr ? nullptr : finalize_hint);
}

if (wrap_type == retrievable) {
CHECK(obj->SetPrivate(context, NAPI_PRIVATE_KEY(context, wrapper),
v8::External::New(isolate, reference)).FromJust());
}

return GET_RETURN_STATUS(env);
}

} // end of namespace v8impl

// Intercepts the Node-V8 module registration callback. Converts parameters
Expand Down Expand Up @@ -2859,41 +2916,12 @@ napi_status napi_wrap(napi_env env,
napi_finalize finalize_cb,
void* finalize_hint,
napi_ref* result) {
NAPI_PREAMBLE(env);
CHECK_ARG(env, js_object);

v8::Isolate* isolate = env->isolate;
v8::Local<v8::Context> context = isolate->GetCurrentContext();

v8::Local<v8::Value> value = v8impl::V8LocalValueFromJsValue(js_object);
RETURN_STATUS_IF_FALSE(env, value->IsObject(), napi_invalid_arg);
v8::Local<v8::Object> obj = value.As<v8::Object>();

// If we've already wrapped this object, we error out.
RETURN_STATUS_IF_FALSE(env,
!obj->HasPrivate(context, NAPI_PRIVATE_KEY(context, wrapper)).FromJust(),
napi_invalid_arg);

v8impl::Reference* reference = nullptr;
if (result != nullptr) {
// The returned reference should be deleted via napi_delete_reference()
// ONLY in response to the finalize callback invocation. (If it is deleted
// before then, then the finalize callback will never be invoked.)
// Therefore a finalize callback is required when returning a reference.
CHECK_ARG(env, finalize_cb);
reference = v8impl::Reference::New(
env, obj, 0, false, finalize_cb, native_object, finalize_hint);
*result = reinterpret_cast<napi_ref>(reference);
} else {
// Create a self-deleting reference.
reference = v8impl::Reference::New(env, obj, 0, true, finalize_cb,
native_object, finalize_cb == nullptr ? nullptr : finalize_hint);
}

CHECK(obj->SetPrivate(context, NAPI_PRIVATE_KEY(context, wrapper),
v8::External::New(isolate, reference)).FromJust());

return GET_RETURN_STATUS(env);
return v8impl::Wrap<v8impl::retrievable>(env,
js_object,
native_object,
finalize_cb,
finalize_hint,
result);
}

napi_status napi_unwrap(napi_env env, napi_value obj, void** result) {
Expand Down Expand Up @@ -4138,3 +4166,17 @@ napi_ref_threadsafe_function(napi_env env, napi_threadsafe_function func) {
CHECK(func != nullptr);
return reinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Ref();
}

napi_status napi_add_finalizer(napi_env env,
napi_value js_object,
void* native_object,
napi_finalize finalize_cb,
void* finalize_hint,
napi_ref* result) {
return v8impl::Wrap<v8impl::anonymous>(env,
js_object,
native_object,
finalize_cb,
finalize_hint,
result);
}
6 changes: 6 additions & 0 deletions src/node_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,12 @@ NAPI_EXTERN napi_status napi_get_value_bigint_words(napi_env env,
int* sign_bit,
size_t* word_count,
uint64_t* words);
NAPI_EXTERN napi_status napi_add_finalizer(napi_env env,
napi_value js_object,
void* native_object,
napi_finalize finalize_cb,
void* finalize_hint,
napi_ref* result);
#endif // NAPI_EXPERIMENTAL

EXTERN_C_END
Expand Down
33 changes: 33 additions & 0 deletions test/addons-napi/test_general/testFinalizer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
'use strict';
// Flags: --expose-gc

const common = require('../../common');
const test_general = require(`./build/${common.buildType}/test_general`);
const assert = require('assert');

let finalized = {};
const callback = common.mustCall(2);

// Add two items to be finalized and ensure the callback is called for each.
test_general.addFinalizerOnly(finalized, callback);
test_general.addFinalizerOnly(finalized, callback);

// Ensure attached items cannot be retrieved.
common.expectsError(() => test_general.unwrap(finalized),
{ type: Error, message: 'Invalid argument' });

// Ensure attached items cannot be removed.
common.expectsError(() => test_general.removeWrap(finalized),
{ type: Error, message: 'Invalid argument' });
finalized = null;
global.gc();

// Add an item to an object that is already wrapped, and ensure that its
// finalizer as well as the wrap finalizer gets called.
let finalizeAndWrap = {};
test_general.wrap(finalizeAndWrap);
test_general.addFinalizerOnly(finalizeAndWrap, common.mustCall());
finalizeAndWrap = null;
global.gc();
assert.strictEqual(test_general.derefItemWasCalled(), true,
'finalize callback was called');
41 changes: 41 additions & 0 deletions test/addons-napi/test_general/test_general.c
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#define NAPI_EXPERIMENTAL
#include <node_api.h>
#include <stdlib.h>
#include "../common.h"
Expand Down Expand Up @@ -177,6 +178,17 @@ static napi_value wrap(napi_env env, napi_callback_info info) {
return NULL;
}

static napi_value unwrap(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value wrapped;
void* data;

NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &wrapped, NULL, NULL));
NAPI_CALL(env, napi_unwrap(env, wrapped, &data));

return NULL;
}

static napi_value remove_wrap(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value wrapped;
Expand Down Expand Up @@ -232,6 +244,33 @@ static napi_value testNapiRun(napi_env env, napi_callback_info info) {
return result;
}

static void finalizer_only_callback(napi_env env, void* data, void* hint) {
napi_ref js_cb_ref = data;
napi_value js_cb, undefined;
NAPI_CALL_RETURN_VOID(env, napi_get_reference_value(env, 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, js_cb_ref));
}

static napi_value add_finalizer_only(napi_env env, napi_callback_info info) {
size_t argc = 2;
napi_value argv[2];
napi_ref js_cb_ref;

NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
NAPI_CALL(env, napi_create_reference(env, argv[1], 1, &js_cb_ref));
NAPI_CALL(env,
napi_add_finalizer(env,
argv[0],
js_cb_ref,
finalizer_only_callback,
NULL,
NULL));
return NULL;
}

static napi_value Init(napi_env env, napi_value exports) {
napi_property_descriptor descriptors[] = {
DECLARE_NAPI_PROPERTY("testStrictEquals", testStrictEquals),
Expand All @@ -246,7 +285,9 @@ static napi_value Init(napi_env env, napi_value exports) {
DECLARE_NAPI_PROPERTY("testNapiErrorCleanup", testNapiErrorCleanup),
DECLARE_NAPI_PROPERTY("testNapiTypeof", testNapiTypeof),
DECLARE_NAPI_PROPERTY("wrap", wrap),
DECLARE_NAPI_PROPERTY("unwrap", unwrap),
DECLARE_NAPI_PROPERTY("removeWrap", remove_wrap),
DECLARE_NAPI_PROPERTY("addFinalizerOnly", add_finalizer_only),
DECLARE_NAPI_PROPERTY("testFinalizeWrap", test_finalize_wrap),
DECLARE_NAPI_PROPERTY("finalizeWasCalled", finalize_was_called),
DECLARE_NAPI_PROPERTY("derefItemWasCalled", deref_item_was_called),
Expand Down