Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
19dad19
deps: float 15d7e79 from openssl
tniessen Jul 21, 2019
e334c1f
src: fix type name in comment
bnoordhuis Jun 20, 2019
386d5d7
lib: support min/max values in validateInteger()
cjihrig Jul 22, 2019
2262526
module: implement "exports" proposal for CommonJS
hybrist Jul 19, 2019
d0b1fb3
doc: api/stream.md typo from writeable to writable
imcotton Jul 23, 2019
e0e7763
crypto: increase maxmem range from 32 to 53 bits
tniessen Jul 21, 2019
a38fecd
tools: update certdata.txt
sam-github Jul 22, 2019
86f4c68
crypto: update root certificates
sam-github Jul 22, 2019
c0a0448
doc: fix type in NSS update instructions
sam-github Jul 22, 2019
24b9d29
build: `uname -m` is amd64 on freebsd, not x86_64
bnoordhuis Jul 22, 2019
3c30456
src : elevate v8 namespaces
HarshithaKP Jul 22, 2019
0667d0c
doc: add documentation for response.flushHeaders()
lpinca Jul 22, 2019
95b87ce
doc: claim NODE_MODULE_VERSION=76 for Electron 8
MarshallOfSound Jul 22, 2019
ae56a23
deps: backport b107214 from upstream V8
addaleax Jul 24, 2019
727ffe4
domain: use strong reference to domain while active
addaleax Jun 20, 2019
881e345
doc: describe why new Buffer() is problematic
sam-github Jul 23, 2019
860c0d8
n-api: add APIs for per-instance state management
Jul 15, 2019
454e879
doc: fix incorrect name in report docs
cjihrig Jul 24, 2019
e0951c8
report: loop over uv_cpu_info() results
cjihrig Jul 23, 2019
d3426ee
assert: avoid potentially misleading reference to object identity
addaleax Jul 23, 2019
b7c6ad5
crypto: add outputLength option to crypto.createHash
tniessen Jul 19, 2019
3a62202
crypto: fix handling of malicious getters (scrypt)
tniessen Jul 23, 2019
5d5c89a
policy: add dependencies map for resources
bmeck Jul 18, 2019
ffc7a00
doc: add documentation for stream.destroyed
ronag Jul 23, 2019
e6b3bfe
n-api: refactor a previous commit
Jul 18, 2019
cf071a0
stream: resolve perf regression introduced by V8 7.3
mcollina Jul 24, 2019
82f263d
build,tools: support building with Visual Studio 2019
targos Jul 20, 2019
f6051f9
test: specialize OOM check for AIX
sam-github Jul 24, 2019
8db43b1
src: move relative uptime init
Jul 24, 2019
c9c7256
http: reset parser.incoming when server response is finished
addaleax Jul 11, 2019
24b8f20
deps: remove backup files
AdamMajer Jul 26, 2019
9e7c662
build: ignore backup files
AdamMajer Jul 26, 2019
a7ef102
crypto: add null check to outputLength logic
cjihrig Jul 26, 2019
0b6a84a
test,report: relax CPU match requirements
addaleax Jul 28, 2019
84efadf
test, util: refactor isObject in test-util
RamirezAlex Jul 27, 2019
5533d48
doc: correct import statement
himself65 Jul 27, 2019
085eb48
doc: fixup esm resolver spec formatting
guybedford Jul 28, 2019
406c50c
src: read break_node_first_line from the inspect options
MarshallOfSound Jun 19, 2019
2142b6d
test: improve test-async-hooks-http-parser-destroy
Flarna Jun 16, 2019
b282c85
vm: increase code coverage of source_text_module.js
kball Jun 21, 2019
9b02f36
deps: dlloads node static linked executable
lal12 Jun 4, 2019
9dfa636
dgram: changed 'var' to 'let' and 'const'
mgochoa Jun 21, 2019
1b0d67b
src: fix OpenBSD build
devnexen Jun 22, 2019
d0d3149
http2: add constant to already destructured constants
dnalborczyk Jun 11, 2019
fa82cbc
http2: destructure constants from require call
dnalborczyk Jun 11, 2019
a28db5f
doc: add example of event close for child_process
ltciro Jun 21, 2019
32cf344
src: readlink("/proc/self/exe") -> uv_exename()
bnoordhuis Jun 21, 2019
a0f89a2
test: refactor test using assert instead of try/catch
juansb827 Jun 21, 2019
9b47f77
test: udpate test comment description
Angelfire Jun 21, 2019
048db38
benchmark: swap var for let in url benchmarks
RamirezAlex Jul 24, 2019
f2c1f36
benchmark: swap var for let in util benchmarks
RamirezAlex Jul 24, 2019
bbcf9f0
benchmark: swap var for let in buffer benchmarks
RamirezAlex Jul 26, 2019
cce2087
src: export v8.GetHeapCodeAndMetadataStatistics()
May 30, 2019
fb57bc4
build: do not mix spaces and tabs in Makefile
lpinca Jul 28, 2019
31aa33b
test: fix race in test-http2-origin
mildsunrise Jul 30, 2019
19070e4
test: fix nits in test/fixtures/tls-connect.js
lpinca Jul 28, 2019
25aa222
build: generate openssl config for BSD-x86
bnoordhuis Jul 22, 2019
3d51d30
src: large pages fix FreeBSD fix region size
devnexen Jul 17, 2019
470db47
build: remove support for s390 (but not s390x)
bnoordhuis Jul 28, 2019
391fe46
benchmark, http: refactor for code consistency
RamirezAlex Jul 21, 2019
464136f
lib: replace var with let in loaders.js
mbj36 Jun 5, 2019
43acce1
worker: handle calling terminate when kHandler is null
elyalvarado Jun 21, 2019
3e63429
doc: add example about emitter.emit in events documentation
felipedc09 Jun 21, 2019
b6e174b
test: use assert.throws() in test-require-json.js
alejandronanez Jun 21, 2019
d72b682
inspector: report all workers
eugeneo Jul 26, 2019
5f07f49
doc: revoke DEP0089
cjihrig Jul 29, 2019
d9084d2
module: unify package exports test for CJS and ESM
hybrist Jul 24, 2019
d601a0a
src: allow generic C++ callables in SetImmediate()
addaleax Jul 15, 2019
7c80963
doc: include "exports" resolver specification
guybedford Dec 11, 2018
c93df0c
n-api: refactoring napi_create_function testing
Jul 28, 2019
c42eb5d
test: refactoring test_error testing
himself65 Jul 30, 2019
efe9b97
test: refactor test-beforeexit-event-exit using mustNotCall
himself65 Jul 30, 2019
dcef7b8
build: include stubs in shared library
jeroen Jul 29, 2019
2236aff
module: exports error as MODULE_NOT_FOUND
guybedford Jul 31, 2019
c389526
test: add tests for spaces in folder names
PaulBags Jul 23, 2019
0ac6d28
doc: writableFinished is true before 'finish'
ronag Jul 23, 2019
f4abf17
doc: remove legacy mode deprecation in assert
Trott Jul 31, 2019
7d9eb17
http2: destroy when settingsFn throws an error
himself65 Jul 31, 2019
57f5d50
doc: fix sorting nit in sections of http.md
vsemozhetbyt Aug 2, 2019
4b91e4d
report: include network interfaces in report
cjihrig Jul 23, 2019
0f8f552
test: refactor test-fs-stat.js
Trott Aug 1, 2019
bdd442f
doc: describe NODE_OPTIONS interop w/cmd line opts
reasonablytall Aug 1, 2019
4a747f6
Revert "src: remove trace_sync_io_ from env"
ChALkeR Aug 1, 2019
3c52dbe
net: shallow copy option when create Server
himself65 Aug 1, 2019
02a50c3
doc: remove use of you
mhdawson Aug 1, 2019
1f82929
path: improve normalization performance
mscdex Aug 3, 2019
11470d5
deps: upgrade npm to 6.10.2
isaacs Jul 25, 2019
77d8f0c
2019-08-06, Version 12.8.0 (Current)
BridgeAR Aug 6, 2019
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 APIs for per-instance state management
Adds `napi_set_instance_data()` and `napi_get_instance_data()`, which
allow native addons to store their data on and retrieve their data from
`napi_env`. `napi_set_instance_data()` accepts a finalizer which is
called when the `node::Environment()` is destroyed.

This entails rendering the `napi_env` local to each add-on.

Fixes: nodejs/abi-stable-node#378
PR-URL: #28682
Reviewed-By: Ben Noordhuis <[email protected]>
Reviewed-By: Michael Dawson <[email protected]>
  • Loading branch information
Gabriel Schulhof authored and targos committed Aug 2, 2019
commit 860c0d89b660b8e464578ef349b6aaa45722d4fc
78 changes: 78 additions & 0 deletions doc/api/n-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,82 @@ NAPI_MODULE_INIT() {
}
```

## Environment Life Cycle APIs

> Stability: 1 - Experimental

[Section 8.7][] of the [ECMAScript Language Specification][] defines the concept
of an "Agent" as a self-contained environment in which JavaScript code runs.
Multiple such Agents may be started and terminated either concurrently or in
sequence by the process.

A Node.js environment corresponds to an ECMAScript Agent. In the main process,
an environment is created at startup, and additional environments can be created
on separate threads to serve as [worker threads][]. When Node.js is embedded in
another application, the main thread of the application may also construct and
destroy a Node.js environment multiple times during the life cycle of the
application process such that each Node.js environment created by the
application may, in turn, during its life cycle create and destroy additional
environments as worker threads.

From the perspective of a native addon this means that the bindings it provides
may be called multiple times, from multiple contexts, and even concurrently from
multiple threads.

Native addons may need to allocate global state of which they make use during
their entire life cycle such that the state must be unique to each instance of
the addon.

To this env, N-API provides a way to allocate data such that its life cycle is
tied to the life cycle of the Agent.

### napi_set_instance_data
<!-- YAML
added: REPLACEME
-->

```C
napi_status napi_set_instance_data(napi_env env,
void* data,
napi_finalize finalize_cb,
void* finalize_hint);
```

- `[in] env`: The environment that the N-API call is invoked under.
- `[in] data`: The data item to make available to bindings of this instance.
- `[in] finalize_cb`: The function to call when the environment is being torn
down. The function receives `data` so that it might free it.
- `[in] finalize_hint`: Optional hint to pass to the finalize callback
during collection.

Returns `napi_ok` if the API succeeded.

This API associates `data` with the currently running Agent. `data` can later
be retrieved using `napi_get_instance_data()`. Any existing data associated with
the currently running Agent which was set by means of a previous call to
`napi_set_instance_data()` will be overwritten. If a `finalize_cb` was provided
by the previous call, it will not be called.

### napi_get_instance_data
<!-- YAML
added: REPLACEME
-->

```C
napi_status napi_get_instance_data(napi_env env,
void** data);
```

- `[in] env`: The environment that the N-API call is invoked under.
- `[out] data`: The data item that was previously associated with the currently
running Agent by a call to `napi_set_instance_data()`.

Returns `napi_ok` if the API succeeded.

This API retrieves data that was previously associated with the currently
running Agent via `napi_set_instance_data()`. If no data is set, the call will
succeed and `data` will be set to `NULL`.

## Basic N-API Data Types

N-API exposes the following fundamental datatypes as abstractions that are
Expand Down Expand Up @@ -4876,6 +4952,7 @@ This API may only be called from the main thread.
[Section 6.1.4]: https://tc39.github.io/ecma262/#sec-ecmascript-language-types-string-type
[Section 6.1.6]: https://tc39.github.io/ecma262/#sec-ecmascript-language-types-number-type
[Section 6.1.7.1]: https://tc39.github.io/ecma262/#table-2
[Section 8.7]: https://tc39.es/ecma262/#sec-agents
[Section 9.1.6]: https://tc39.github.io/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-defineownproperty-p-desc
[Working with JavaScript Functions]: #n_api_working_with_javascript_functions
[Working with JavaScript Properties]: #n_api_working_with_javascript_properties
Expand Down Expand Up @@ -4930,3 +5007,4 @@ This API may only be called from the main thread.
[`uv_unref`]: http://docs.libuv.org/en/v1.x/handle.html#c.uv_unref
[async_hooks `type`]: async_hooks.html#async_hooks_type
[context-aware addons]: addons.html#addons_context_aware_addons
[worker threads]: https://nodejs.org/api/worker_threads.html
1 change: 0 additions & 1 deletion src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,6 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2;
V(contextify_context_private_symbol, "node:contextify:context") \
V(contextify_global_private_symbol, "node:contextify:global") \
V(decorated_private_symbol, "node:decorated") \
V(napi_env, "node:napi:env") \
V(napi_wrapper, "node:napi:wrapper") \
V(sab_lifetimepartner_symbol, "node:sharedArrayBufferLifetimePartner") \

Expand Down
9 changes: 9 additions & 0 deletions src/js_native_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,15 @@ NAPI_EXTERN napi_status napi_add_finalizer(napi_env env,
napi_finalize finalize_cb,
void* finalize_hint,
napi_ref* result);

// Instance data
NAPI_EXTERN napi_status napi_set_instance_data(napi_env env,
void* data,
napi_finalize finalize_cb,
void* finalize_hint);

NAPI_EXTERN napi_status napi_get_instance_data(napi_env env,
void** data);
#endif // NAPI_EXPERIMENTAL

EXTERN_C_END
Expand Down
33 changes: 28 additions & 5 deletions src/js_native_api_v8.cc
Original file line number Diff line number Diff line change
Expand Up @@ -305,12 +305,10 @@ class Reference : private Finalizer {
static void SecondPassCallback(const v8::WeakCallbackInfo<Reference>& data) {
Reference* reference = data.GetParameter();

napi_env env = reference->_env;

if (reference->_finalize_callback != nullptr) {
NapiCallIntoModuleThrow(env, [&]() {
reference->_env->CallIntoModuleThrow([&](napi_env env) {
reference->_finalize_callback(
reference->_env,
env,
reference->_finalize_data,
reference->_finalize_hint);
});
Expand Down Expand Up @@ -452,7 +450,9 @@ class CallbackWrapperBase : public CallbackWrapper {
napi_callback cb = _bundle->*FunctionField;

napi_value result;
NapiCallIntoModuleThrow(env, [&]() { result = cb(env, cbinfo_wrapper); });
env->CallIntoModuleThrow([&](napi_env env) {
result = cb(env, cbinfo_wrapper);
});

if (result != nullptr) {
this->SetReturnValue(result);
Expand Down Expand Up @@ -2986,3 +2986,26 @@ napi_status napi_adjust_external_memory(napi_env env,

return napi_clear_last_error(env);
}

napi_status napi_set_instance_data(napi_env env,
void* data,
napi_finalize finalize_cb,
void* finalize_hint) {
CHECK_ENV(env);

env->instance_data.data = data;
env->instance_data.finalize_cb = finalize_cb;
env->instance_data.hint = finalize_hint;

return napi_clear_last_error(env);
}

napi_status napi_get_instance_data(napi_env env,
void** data) {
CHECK_ENV(env);
CHECK_ARG(env, data);

*data = env->instance_data.data;

return napi_clear_last_error(env);
}
57 changes: 35 additions & 22 deletions src/js_native_api_v8.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,21 @@
#include "js_native_api_types.h"
#include "js_native_api_v8_internals.h"

static napi_status napi_clear_last_error(napi_env env);

struct napi_env__ {
explicit napi_env__(v8::Local<v8::Context> context)
: isolate(context->GetIsolate()),
context_persistent(isolate, context) {
CHECK_EQ(isolate, context->GetIsolate());
}
virtual ~napi_env__() = default;
virtual ~napi_env__() {
if (instance_data.finalize_cb != nullptr) {
CallIntoModuleThrow([&](napi_env env) {
instance_data.finalize_cb(env, instance_data.data, instance_data.hint);
});
}
}
v8::Isolate* const isolate; // Shortcut for context()->GetIsolate()
v8impl::Persistent<v8::Context> context_persistent;

Expand All @@ -25,11 +33,37 @@ struct napi_env__ {

virtual bool can_call_into_js() const { return true; }

template <typename T, typename U>
void CallIntoModule(T&& call, U&& handle_exception) {
int open_handle_scopes_before = open_handle_scopes;
int open_callback_scopes_before = open_callback_scopes;
napi_clear_last_error(this);
call(this);
CHECK_EQ(open_handle_scopes, open_handle_scopes_before);
CHECK_EQ(open_callback_scopes, open_callback_scopes_before);
if (!last_exception.IsEmpty()) {
handle_exception(this, last_exception.Get(this->isolate));
last_exception.Reset();
}
}

template <typename T>
void CallIntoModuleThrow(T&& call) {
CallIntoModule(call, [&](napi_env env, v8::Local<v8::Value> value) {
env->isolate->ThrowException(value);
});
}

v8impl::Persistent<v8::Value> last_exception;
napi_extended_error_info last_error;
int open_handle_scopes = 0;
int open_callback_scopes = 0;
int refs = 1;
struct {
void* data = nullptr;
void* hint = nullptr;
napi_finalize finalize_cb = nullptr;
} instance_data;
};

static inline napi_status napi_clear_last_error(napi_env env) {
Expand Down Expand Up @@ -114,27 +148,6 @@ napi_status napi_set_last_error(napi_env env, napi_status error_code,
} \
} while (0)

template <typename T, typename U>
void NapiCallIntoModule(napi_env env, T&& call, U&& handle_exception) {
int open_handle_scopes = env->open_handle_scopes;
int open_callback_scopes = env->open_callback_scopes;
napi_clear_last_error(env);
call();
CHECK_EQ(env->open_handle_scopes, open_handle_scopes);
CHECK_EQ(env->open_callback_scopes, open_callback_scopes);
if (!env->last_exception.IsEmpty()) {
handle_exception(env->last_exception.Get(env->isolate));
env->last_exception.Reset();
}
}

template <typename T>
void NapiCallIntoModuleThrow(napi_env env, T&& call) {
NapiCallIntoModule(env, call, [&](v8::Local<v8::Value> value) {
env->isolate->ThrowException(value);
});
}

namespace v8impl {

//=== Conversion between V8 Handles and napi_value ========================
Expand Down
74 changes: 23 additions & 51 deletions src/node_api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ class BufferFinalizer : private Finalizer {
v8::HandleScope handle_scope(finalizer->_env->isolate);
v8::Context::Scope context_scope(finalizer->_env->context());

NapiCallIntoModuleThrow(finalizer->_env, [&]() {
finalizer->_env->CallIntoModuleThrow([&](napi_env env) {
finalizer->_finalize_callback(
finalizer->_env,
env,
finalizer->_finalize_data,
finalizer->_finalize_hint);
});
Expand All @@ -59,44 +59,22 @@ class BufferFinalizer : private Finalizer {
}
};

static inline napi_env GetEnv(v8::Local<v8::Context> context) {
static inline napi_env NewEnv(v8::Local<v8::Context> context) {
node_napi_env result;

auto isolate = context->GetIsolate();
auto global = context->Global();

// In the case of the string for which we grab the private and the value of
// the private on the global object we can call .ToLocalChecked() directly
// because we need to stop hard if either of them is empty.
//
// Re https://github.com/nodejs/node/pull/14217#discussion_r128775149
auto value = global->GetPrivate(context, NAPI_PRIVATE_KEY(context, env))
.ToLocalChecked();

if (value->IsExternal()) {
result = static_cast<node_napi_env>(value.As<v8::External>()->Value());
} else {
result = new node_napi_env__(context);
auto external = v8::External::New(isolate, result);

// We must also stop hard if the result of assigning the env to the global
// is either nothing or false.
CHECK(global->SetPrivate(context, NAPI_PRIVATE_KEY(context, env), external)
.FromJust());

// TODO(addaleax): There was previously code that tried to delete the
// napi_env when its v8::Context was garbage collected;
// However, as long as N-API addons using this napi_env are in place,
// the Context needs to be accessible and alive.
// Ideally, we'd want an on-addon-unload hook that takes care of this
// once all N-API addons using this napi_env are unloaded.
// For now, a per-Environment cleanup hook is the best we can do.
result->node_env()->AddCleanupHook(
[](void* arg) {
static_cast<napi_env>(arg)->Unref();
},
static_cast<void*>(result));
}
result = new node_napi_env__(context);
// TODO(addaleax): There was previously code that tried to delete the
// napi_env when its v8::Context was garbage collected;
// However, as long as N-API addons using this napi_env are in place,
// the Context needs to be accessible and alive.
// Ideally, we'd want an on-addon-unload hook that takes care of this
// once all N-API addons using this napi_env are unloaded.
// For now, a per-Environment cleanup hook is the best we can do.
result->node_env()->AddCleanupHook(
[](void* arg) {
static_cast<napi_env>(arg)->Unref();
},
static_cast<void*>(result));

return result;
}
Expand Down Expand Up @@ -325,7 +303,7 @@ class ThreadSafeFunction : public node::AsyncResource {
v8::Local<v8::Function>::New(env->isolate, ref);
js_callback = v8impl::JsValueFromV8LocalValue(js_cb);
}
NapiCallIntoModuleThrow(env, [&]() {
env->CallIntoModuleThrow([&](napi_env env) {
call_js_cb(env, js_callback, context, data);
});
}
Expand All @@ -346,7 +324,7 @@ class ThreadSafeFunction : public node::AsyncResource {
v8::HandleScope scope(env->isolate);
if (finalize_cb) {
CallbackScope cb_scope(this);
NapiCallIntoModuleThrow(env, [&]() {
env->CallIntoModuleThrow([&](napi_env env) {
finalize_cb(env, finalize_data, context);
});
}
Expand Down Expand Up @@ -481,10 +459,10 @@ void napi_module_register_by_symbol(v8::Local<v8::Object> exports,

// Create a new napi_env for this module or reference one if a pre-existing
// one is found.
napi_env env = v8impl::GetEnv(context);
napi_env env = v8impl::NewEnv(context);

napi_value _exports;
NapiCallIntoModuleThrow(env, [&]() {
env->CallIntoModuleThrow([&](napi_env env) {
_exports = init(env, v8impl::JsValueFromV8LocalValue(exports));
});

Expand Down Expand Up @@ -889,15 +867,9 @@ class Work : public node::AsyncResource, public node::ThreadPoolWork {

CallbackScope callback_scope(this);

// We have to back up the env here because the `NAPI_CALL_INTO_MODULE` macro
// makes use of it after the call into the module completes, but the module
// may have deallocated **this**, and along with it the place where _env is
// stored.
napi_env env = _env;

NapiCallIntoModule(env, [&]() {
_complete(_env, ConvertUVErrorCode(status), _data);
}, [env](v8::Local<v8::Value> local_err) {
_env->CallIntoModule([&](napi_env env) {
_complete(env, ConvertUVErrorCode(status), _data);
}, [](napi_env env, v8::Local<v8::Value> local_err) {
// If there was an unhandled exception in the complete callback,
// report it as a fatal exception. (There is no JavaScript on the
// callstack that can possibly handle it.)
Expand Down
Loading