From a1aff28fba251e2104c97c6d3a23e2452dc05304 Mon Sep 17 00:00:00 2001 From: Peter Marshall Date: Fri, 14 Dec 2018 13:09:59 +0100 Subject: [PATCH 01/34] deps: cherry-pick 525b396 from V8 upstream Original commit message: [cpu-profiler] Fix a leak caused by re-logging existing functions. Don't re-log all existing functions during StartProcessorIfNotStarted(). They will already be in the CodeMap attached to the ProfileGenerator and re-logging them causes leaks. See the linked bug for more details. Bug: v8:8253 Change-Id: Ibb1a1ab2431c588e8c3a3a9ff714767cdf61a88e Reviewed-on: https://chromium-review.googlesource.com/1256763 Commit-Queue: Peter Marshall Reviewed-by: Yang Guo Cr-Commit-Position: refs/heads/master@{#56336} Refs: https://github.com/v8/v8/commit/525b39619548b4e7fbf48934acb18d3f209321db PR-URL: https://github.com/nodejs/node/pull/25041 Reviewed-By: Yang Guo Reviewed-By: Rod Vagg --- deps/v8/include/v8-version.h | 2 +- deps/v8/src/profiler/cpu-profiler.cc | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/deps/v8/include/v8-version.h b/deps/v8/include/v8-version.h index 62c81f2ca057ad..85a14e37f2e4f3 100644 --- a/deps/v8/include/v8-version.h +++ b/deps/v8/include/v8-version.h @@ -11,7 +11,7 @@ #define V8_MAJOR_VERSION 6 #define V8_MINOR_VERSION 2 #define V8_BUILD_NUMBER 414 -#define V8_PATCH_LEVEL 75 +#define V8_PATCH_LEVEL 76 // Use 1 for candidates and 0 otherwise. // (Boolean macro values are not supported by all preprocessors.) diff --git a/deps/v8/src/profiler/cpu-profiler.cc b/deps/v8/src/profiler/cpu-profiler.cc index 69021cee344eb2..17c3e53d3d0455 100644 --- a/deps/v8/src/profiler/cpu-profiler.cc +++ b/deps/v8/src/profiler/cpu-profiler.cc @@ -326,8 +326,11 @@ void CpuProfiler::StartProcessorIfNotStarted() { // Disable logging when using the new implementation. saved_is_logging_ = logger->is_logging_; logger->is_logging_ = false; + + bool codemap_needs_initialization = false; if (!generator_) { generator_.reset(new ProfileGenerator(profiles_.get())); + codemap_needs_initialization = true; CreateEntriesForRuntimeCallStats(); } processor_.reset(new ProfilerEventsProcessor(isolate_, generator_.get(), @@ -341,12 +344,14 @@ void CpuProfiler::StartProcessorIfNotStarted() { isolate_->set_is_profiling(true); // Enumerate stuff we already have in the heap. DCHECK(isolate_->heap()->HasBeenSetUp()); - if (!FLAG_prof_browser_mode) { - logger->LogCodeObjects(); + if (codemap_needs_initialization) { + if (!FLAG_prof_browser_mode) { + logger->LogCodeObjects(); + } + logger->LogCompiledFunctions(); + logger->LogAccessorCallbacks(); + LogBuiltins(); } - logger->LogCompiledFunctions(); - logger->LogAccessorCallbacks(); - LogBuiltins(); // Enable stack sampling. processor_->AddCurrentStack(isolate_); processor_->StartSynchronously(); From b17819db3d58693c40a5711bbaa50592fc638816 Mon Sep 17 00:00:00 2001 From: James Bunton Date: Wed, 19 Dec 2018 14:54:58 +1100 Subject: [PATCH 02/34] doc: update the http.request.setTimeout docs to be accurate Refs: https://github.com/nodejs/node/pull/8895 PR-URL: https://github.com/nodejs/node/pull/25123 Reviewed-By: Luigi Pinca Reviewed-By: James M Snell --- doc/api/http.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/api/http.md b/doc/api/http.md index 0ad9f8a2a45ff8..8af25b4e02efb2 100644 --- a/doc/api/http.md +++ b/doc/api/http.md @@ -625,8 +625,9 @@ added: v0.5.9 * `timeout` {number} Milliseconds before a request times out. * `callback` {Function} Optional function to be called when a timeout occurs. Same as binding to the `timeout` event. -Once a socket is assigned to this request and is connected -[`socket.setTimeout()`][] will be called. +If no socket is assigned to this request then [`socket.setTimeout()`][] will be +called immediately. Otherwise [`socket.setTimeout()`][] will be called after the +assigned socket is connected. Returns `request`. From fbafe8d311b4abd3154ded212075c2819cbd360a Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Wed, 26 Dec 2018 13:21:57 +0100 Subject: [PATCH 03/34] test: fix test-repl-envvars MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In 180f86507d496b11aa35b2df4594629a92cce329, the test was changed so that the `env` argument of `createInternalRepl()` also contained external environment variables, because keeping them can be necessary for spawning processes on some systems. However, this test does not spawn new processes, and relies on the fact that the environment variables it tests are not already set (and fails otherwise); therefore, reverting to the original state should fix this. Fixes: https://github.com/nodejs/node/issues/21451 Fixes: https://github.com/nodejs/build/issues/1377 Refs: https://github.com/nodejs/node/pull/25219 PR-URL: https://github.com/nodejs/node/pull/25226 Reviewed-By: Rich Trott Reviewed-By: Tobias Nießen Reviewed-By: Benjamin Gruenbaum Reviewed-By: Denys Otrishko Reviewed-By: Colin Ihrig Reviewed-By: Luigi Pinca --- test/parallel/test-repl-envvars.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/parallel/test-repl-envvars.js b/test/parallel/test-repl-envvars.js index d29e7b3574c1f2..c4efd184c4c91c 100644 --- a/test/parallel/test-repl-envvars.js +++ b/test/parallel/test-repl-envvars.js @@ -36,7 +36,7 @@ const tests = [ ]; function run(test) { - const env = Object.assign({}, process.env, test.env); + const env = test.env; const expected = test.expected; const opts = { terminal: true, From 759a0180b5194686692caab955e371c5efc82e4e Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Sun, 17 Jun 2018 11:52:49 -0400 Subject: [PATCH 04/34] n-api: add API for asynchronous functions Bundle a `uv_async_t`, a `uv_idle_t`, a `uv_mutex_t`, a `uv_cond_t`, and a `v8::Persistent` to make it possible to call into JS from another thread. The API accepts a void data pointer and a callback which will be invoked on the loop thread and which will receive the `napi_value` representing the JavaScript function to call so as to perform the call into JS. The callback is run inside a `node::CallbackScope`. A `std::queue` is used to store calls from the secondary threads, and an idle loop is started by the `uv_async_t` callback on the loop thread to drain the queue, calling into JS with each item. Items can be added to the queue blockingly or non-blockingly. The thread-safe function can be referenced or unreferenced, with the same semantics as libuv handles. Re: https://github.com/nodejs/help/issues/1035 Re: https://github.com/nodejs/node/issues/20964 Fixes: https://github.com/nodejs/node/issues/13512 Backport-PR-URL: https://github.com/nodejs/node/pull/25002 PR-URL: https://github.com/nodejs/node/pull/17887 Reviewed-By: Matteo Collina Reviewed-By: Michael Dawson --- doc/api/errors.md | 25 + doc/api/n-api.md | 372 ++++++++++++++- src/node_api.cc | 443 +++++++++++++++++- src/node_api.h | 38 ++ src/node_api_types.h | 28 +- .../test_threadsafe_function/binding.c | 254 ++++++++++ .../test_threadsafe_function/binding.gyp | 8 + .../test_threadsafe_function/test.js | 166 +++++++ 8 files changed, 1330 insertions(+), 4 deletions(-) create mode 100644 test/addons-napi/test_threadsafe_function/binding.c create mode 100644 test/addons-napi/test_threadsafe_function/binding.gyp create mode 100644 test/addons-napi/test_threadsafe_function/test.js diff --git a/doc/api/errors.md b/doc/api/errors.md index 53ef8395ac7065..590c504ae68edf 100755 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -1107,6 +1107,31 @@ multiple of the element size. While calling `napi_create_typedarray()`, `(length * size_of_element) + byte_offset` was larger than the length of given `buffer`. + +### ERR_NAPI_TSFN_CALL_JS + +An error occurred while invoking the JavaScript portion of the thread-safe +function. + + +### ERR_NAPI_TSFN_GET_UNDEFINED + +An error occurred while attempting to retrieve the JavaScript `undefined` +value. + + +### ERR_NAPI_TSFN_START_IDLE_LOOP + +On the main thread, values are removed from the queue associated with the +thread-safe function in an idle loop. This error indicates that an error +has occurred when attemping to start the loop. + + +### ERR_NAPI_TSFN_STOP_IDLE_LOOP + +Once no more items are left in the queue, the idle loop must be suspended. This +error indicates that the idle loop has failed to stop. + ### ERR_NO_ICU diff --git a/doc/api/n-api.md b/doc/api/n-api.md index 1e43e4f224d9f0..7e7ed9efd3e88a 100644 --- a/doc/api/n-api.md +++ b/doc/api/n-api.md @@ -90,7 +90,11 @@ typedef enum { napi_cancelled, napi_escape_called_twice, napi_handle_scope_mismatch, - napi_callback_scope_mismatch + napi_callback_scope_mismatch, +#ifdef NAPI_EXPERIMENTAL + napi_queue_full, + napi_closing, +#endif // NAPI_EXPERIMENTAL } napi_status; ``` If additional information is required upon an API returning a failed status, @@ -128,6 +132,43 @@ not allowed. ### napi_value This is an opaque pointer that is used to represent a JavaScript value. +### napi_threadsafe_function + +> Stability: 1 - Experimental + +This is an opaque pointer that represents a JavaScript function which can be +called asynchronously from multiple threads via +`napi_call_threadsafe_function()`. + +### napi_threadsafe_function_release_mode + +> Stability: 1 - Experimental + +A value to be given to `napi_release_threadsafe_function()` to indicate whether +the thread-safe function is to be closed immediately (`napi_tsfn_abort`) or +merely released (`napi_tsfn_release`) and thus available for subsequent use via +`napi_acquire_threadsafe_function()` and `napi_call_threadsafe_function()`. +```C +typedef enum { + napi_tsfn_release, + napi_tsfn_abort +} napi_threadsafe_function_release_mode; +``` + +### napi_threadsafe_function_call_mode + +> Stability: 1 - Experimental + +A value to be given to `napi_call_threadsafe_function()` to indicate whether +the call should block whenever the queue associated with the thread-safe +function is full. +```C +typedef enum { + napi_tsfn_nonblocking, + napi_tsfn_blocking +} napi_threadsafe_function_call_mode; +``` + ### N-API Memory Management types #### napi_handle_scope This is an abstraction used to control and modify the lifetime of objects @@ -205,6 +246,43 @@ typedef void (*napi_async_complete_callback)(napi_env env, void* data); ``` +#### napi_threadsafe_function_call_js + +> Stability: 1 - Experimental + +Function pointer used with asynchronous thread-safe function calls. The callback +will be called on the main thread. Its purpose is to use a data item arriving +via the queue from one of the secondary threads to construct the parameters +necessary for a call into JavaScript, usually via `napi_call_function`, and then +make the call into JavaScript. + +The data arriving from the secondary thread via the queue is given in the `data` +parameter and the JavaScript function to call is given in the `js_callback` +parameter. + +N-API sets up the environment prior to calling this callback, so it is +sufficient to call the JavaScript function via `napi_call_function` rather than +via `napi_make_callback`. + +Callback functions must satisfy the following signature: +```C +typedef void (*napi_threadsafe_function_call_js)(napi_env env, + napi_value js_callback, + void* context, + void* data); +``` +- `[in] env`: The environment to use for API calls, or `NULL` if the thread-safe +function is being torn down and `data` may need to be freed. +- `[in] js_callback`: The JavaScript function to call, or `NULL` if the +thread-safe function is being torn down and `data` may need to be freed. +- `[in] context`: The optional data with which the thread-safe function was +created. +- `[in] data`: Data created by the secondary thread. It is the responsibility of +the callback to convert this native data to JavaScript values (with N-API +functions) that can be passed as parameters when `js_callback` is invoked. This +pointer is managed entirely by the threads and this callback. Thus this callback +should free the data. + ## Error Handling N-API uses both return values and JavaScript exceptions for error handling. The following sections explain the approach for each case. @@ -3837,6 +3915,296 @@ NAPI_EXTERN napi_status napi_get_uv_event_loop(napi_env env, - `[in] env`: The environment that the API is invoked under. - `[out] loop`: The current libuv loop instance. +## Asynchronous Thread-safe Function Calls + +> Stability: 1 - Experimental + +JavaScript functions can normally only be called from a native addon's main +thread. If an addon creates additional threads, then N-API functions that +require a `napi_env`, `napi_value`, or `napi_ref` must not be called from those +threads. + +When an addon has additional threads and JavaScript functions need to be invoked +based on the processing completed by those threads, those threads must +communicate with the addon's main thread so that the main thread can invoke the +JavaScript function on their behalf. The thread-safe function APIs provide an +easy way to do this. + +These APIs provide the type `napi_threadsafe_function` as well as APIs to +create, destroy, and call objects of this type. +`napi_create_threadsafe_function()` creates a persistent reference to a +`napi_value` that holds a JavaScript function which can be called from multiple +threads. The calls happen asynchronously. This means that values with which the +JavaScript callback is to be called will be placed in a queue, and, for each +value in the queue, a call will eventually be made to the JavaScript function. + +Upon creation of a `napi_threadsafe_function` a `napi_finalize` callback can be +provided. This callback will be invoked on the main thread when the thread-safe +function is about to be destroyed. It receives the context and the finalize data +given during construction, and provides an opportunity for cleaning up after the +threads e.g. by calling `uv_thread_join()`. **It is important that, aside from +the main loop thread, there be no threads left using the thread-safe function +after the finalize callback completes.** + +The `context` given during the call to `napi_create_threadsafe_function()` can +be retrieved from any thread with a call to +`napi_get_threadsafe_function_context()`. + +`napi_call_threadsafe_function()` can then be used for initiating a call into +JavaScript. `napi_call_threadsafe_function()` accepts a parameter which controls +whether the API behaves blockingly. If set to `napi_tsfn_nonblocking`, the API +behaves non-blockingly, returning `napi_queue_full` if the queue was full, +preventing data from being successfully added to the queue. If set to +`napi_tsfn_blocking`, the API blocks until space becomes available in the queue. +`napi_call_threadsafe_function()` never blocks if the thread-safe function was +created with a maximum queue size of 0. + +The actual call into JavaScript is controlled by the callback given via the +`call_js_cb` parameter. `call_js_cb` is invoked on the main thread once for each +value that was placed into the queue by a successful call to +`napi_call_threadsafe_function()`. If such a callback is not given, a default +callback will be used, and the resulting JavaScript call will have no arguments. +The `call_js_cb` callback receives the JavaScript function to call as a +`napi_value` in its parameters, as well as the `void*` context pointer used when +creating the `napi_threadsafe_function`, and the next data pointer that was +created by one of the secondary threads. The callback can then use an API such +as `napi_call_function()` to call into JavaScript. + +The callback may also be invoked with `env` and `call_js_cb` both set to `NULL` +to indicate that calls into JavaScript are no longer possible, while items +remain in the queue that may need to be freed. This normally occurs when the +Node.js process exits while there is a thread-safe function still active. + +It is not necessary to call into JavaScript via `napi_make_callback()` because +N-API runs `call_js_cb` in a context appropriate for callbacks. + +Threads can be added to and removed from a `napi_threadsafe_function` object +during its existence. Thus, in addition to specifying an initial number of +threads upon creation, `napi_acquire_threadsafe_function` can be called to +indicate that a new thread will start making use of the thread-safe function. +Similarly, `napi_release_threadsafe_function` can be called to indicate that an +existing thread will stop making use of the thread-safe function. + +`napi_threadsafe_function` objects are destroyed when every thread which uses +the object has called `napi_release_threadsafe_function()` or has received a +return status of `napi_closing` in response to a call to +`napi_call_threadsafe_function`. The queue is emptied before the +`napi_threadsafe_function` is destroyed. It is important that +`napi_release_threadsafe_function()` be the last API call made in conjunction +with a given `napi_threadsafe_function`, because after the call completes, there +is no guarantee that the `napi_threadsafe_function` is still allocated. For the +same reason it is also important that no more use be made of a thread-safe +function after receiving a return value of `napi_closing` in response to a call +to `napi_call_threadsafe_function`. Data associated with the +`napi_threadsafe_function` can be freed in its `napi_finalize` callback which +was passed to `napi_create_threadsafe_function()`. + +Once the number of threads making use of a `napi_threadsafe_function` reaches +zero, no further threads can start making use of it by calling +`napi_acquire_threadsafe_function()`. In fact, all subsequent API calls +associated with it, except `napi_release_threadsafe_function()`, will return an +error value of `napi_closing`. + +The thread-safe function can be "aborted" by giving a value of `napi_tsfn_abort` +to `napi_release_threadsafe_function()`. This will cause all subsequent APIs +associated with the thread-safe function except +`napi_release_threadsafe_function()` to return `napi_closing` even before its +reference count reaches zero. In particular, `napi_call_threadsafe_function()` +will return `napi_closing`, thus informing the threads that it is no longer +possible to make asynchronous calls to the thread-safe function. This can be +used as a criterion for terminating the thread. **Upon receiving a return value +of `napi_closing` from `napi_call_threadsafe_function()` a thread must make no +further use of the thread-safe function because it is no longer guaranteed to +be allocated.** + +Similarly to libuv handles, thread-safe functions can be "referenced" and +"unreferenced". A "referenced" thread-safe function will cause the event loop on +the thread on which it is created to remain alive until the thread-safe function +is destroyed. In contrast, an "unreferenced" thread-safe function will not +prevent the event loop from exiting. The APIs `napi_ref_threadsafe_function` and +`napi_unref_threadsafe_function` exist for this purpose. + +### napi_create_threadsafe_function + +> Stability: 1 - Experimental + + +```C +NAPI_EXTERN napi_status +napi_create_threadsafe_function(napi_env env, + napi_value func, + napi_value async_resource, + napi_value async_resource_name, + size_t max_queue_size, + size_t initial_thread_count, + void* thread_finalize_data, + napi_finalize thread_finalize_cb, + void* context, + napi_threadsafe_function_call_js call_js_cb, + napi_threadsafe_function* result); +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] func`: The JavaScript function to call from another thread. +- `[in] async_resource`: An optional object associated with the async work that +will be passed to possible `async_hooks` [`init` hooks][]. +- `[in] async_resource_name`: A javaScript string to provide an identifier for +the kind of resource that is being provided for diagnostic information exposed +by the `async_hooks` API. +- `[in] max_queue_size`: Maximum size of the queue. 0 for no limit. +- `[in] initial_thread_count`: The initial number of threads, including the main +thread, which will be making use of this function. +- `[in] thread_finalize_data`: Data to be passed to `thread_finalize_cb`. +- `[in] thread_finalize_cb`: Function to call when the +`napi_threadsafe_function` is being destroyed. +- `[in] context`: Optional data to attach to the resulting +`napi_threadsafe_function`. +- `[in] call_js_cb`: Optional callback which calls the JavaScript function in +response to a call on a different thread. This callback will be called on the +main thread. If not given, the JavaScript function will be called with no +parameters and with `undefined` as its `this` value. +- `[out] result`: The asynchronous thread-safe JavaScript function. + +### napi_get_threadsafe_function_context + +> Stability: 1 - Experimental + + +```C +NAPI_EXTERN napi_status +napi_get_threadsafe_function_context(napi_threadsafe_function func, + void** result); +``` + +- `[in] func`: The thread-safe function for which to retrieve the context. +- `[out] context`: The location where to store the context. + +This API may be called from any thread which makes use of `func`. + +### napi_call_threadsafe_function + +> Stability: 1 - Experimental + + +```C +NAPI_EXTERN napi_status +napi_call_threadsafe_function(napi_threadsafe_function func, + void* data, + napi_threadsafe_function_call_mode is_blocking); +``` + +- `[in] func`: The asynchronous thread-safe JavaScript function to invoke. +- `[in] data`: Data to send into JavaScript via the callback `call_js_cb` +provided during the creation of the thread-safe JavaScript function. +- `[in] is_blocking`: Flag whose value can be either `napi_tsfn_blocking` to +indicate that the call should block if the queue is full or +`napi_tsfn_nonblocking` to indicate that the call should return immediately with +a status of `napi_queue_full` whenever the queue is full. + +This API will return `napi_closing` if `napi_release_threadsafe_function()` was +called with `abort` set to `napi_tsfn_abort` from any thread. The value is only +added to the queue if the API returns `napi_ok`. + +This API may be called from any thread which makes use of `func`. + +### napi_acquire_threadsafe_function + +> Stability: 1 - Experimental + + +```C +NAPI_EXTERN napi_status +napi_acquire_threadsafe_function(napi_threadsafe_function func); +``` + +- `[in] func`: The asynchronous thread-safe JavaScript function to start making +use of. + +A thread should call this API before passing `func` to any other thread-safe +function APIs to indicate that it will be making use of `func`. This prevents +`func` from being destroyed when all other threads have stopped making use of +it. + +This API may be called from any thread which will start making use of `func`. + +### napi_release_threadsafe_function + +> Stability: 1 - Experimental + + +```C +NAPI_EXTERN napi_status +napi_release_threadsafe_function(napi_threadsafe_function func, + napi_threadsafe_function_release_mode mode); +``` + +- `[in] func`: The asynchronous thread-safe JavaScript function whose reference +count to decrement. +- `[in] mode`: Flag whose value can be either `napi_tsfn_release` to indicate +that the current thread will make no further calls to the thread-safe function, +or `napi_tsfn_abort` to indicate that in addition to the current thread, no +other thread should make any further calls to the thread-safe function. If set +to `napi_tsfn_abort`, further calls to `napi_call_threadsafe_function()` will +return `napi_closing`, and no further values will be placed in the queue. + +A thread should call this API when it stops making use of `func`. Passing `func` +to any thread-safe APIs after having called this API has undefined results, as +`func` may have been destroyed. + +This API may be called from any thread which will stop making use of `func`. + +### napi_ref_threadsafe_function + +> Stability: 1 - Experimental + + +```C +NAPI_EXTERN napi_status +napi_ref_threadsafe_function(napi_env env, napi_threadsafe_function func); +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] func`: The thread-safe function to reference. + +This API is used to indicate that the event loop running on the main thread +should not exit until `func` has been destroyed. Similar to [`uv_ref`][] it is +also idempotent. + +This API may only be called from the main thread. + +### napi_unref_threadsafe_function + +> Stability: 1 - Experimental + + +```C +NAPI_EXTERN napi_status +napi_unref_threadsafe_function(napi_env env, napi_threadsafe_function func); +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] func`: The thread-safe function to unreference. + +This API is used to indicate that the event loop running on the main thread +may exit before `func` is destroyed. Similar to [`uv_unref`][] it is also +idempotent. + +This API may only be called from the main thread. + [Basic N-API Data Types]: #n_api_basic_n_api_data_types [Custom Asynchronous Operations]: #n_api_custom_asynchronous_operations [ECMAScript Language Specification]: https://tc39.github.io/ecma262/ @@ -3899,5 +4267,7 @@ NAPI_EXTERN napi_status napi_get_uv_event_loop(napi_env env, [`napi_throw`]: #n_api_napi_throw [`napi_unwrap`]: #n_api_napi_unwrap [`napi_wrap`]: #n_api_napi_wrap +[`uv_ref`]: http://docs.libuv.org/en/v1.x/handle.html#c.uv_ref +[`uv_unref`]: http://docs.libuv.org/en/v1.x/handle.html#c.uv_unref [`process.release`]: process.html#process_process_release [async_hooks `type`]: async_hooks.html#async_hooks_type diff --git a/src/node_api.cc b/src/node_api.cc index 61e9f6b4b00279..20fb2f8c9a0af8 100644 --- a/src/node_api.cc +++ b/src/node_api.cc @@ -5,6 +5,7 @@ #include #include #include +#define NAPI_EXPERIMENTAL #include "node_api.h" #include "node_internals.h" #include "env.h" @@ -947,7 +948,10 @@ const char* error_messages[] = {nullptr, "The async work item was cancelled", "napi_escape_handle already called on scope", "Invalid handle scope usage", - "Invalid callback scope usage"}; + "Invalid callback scope usage", + "Thread-safe function queue is full", + "Thread-safe function handle is closing" +}; static inline napi_status napi_clear_last_error(napi_env env) { env->last_error.error_code = napi_ok; @@ -978,7 +982,7 @@ napi_status napi_get_last_error_info(napi_env env, // We don't have a napi_status_last as this would result in an ABI // change each time a message was added. static_assert( - node::arraysize(error_messages) == napi_callback_scope_mismatch + 1, + node::arraysize(error_messages) == napi_closing + 1, "Count of error messages must match count of error values"); CHECK_LE(env->last_error.error_code, napi_callback_scope_mismatch); @@ -3587,3 +3591,438 @@ napi_status napi_run_script(napi_env env, *result = v8impl::JsValueFromV8LocalValue(script_result.ToLocalChecked()); return GET_RETURN_STATUS(env); } + +class TsFn: public node::AsyncResource { + public: + TsFn(v8::Local func, + v8::Local resource, + v8::Local name, + size_t thread_count_, + void* context_, + size_t max_queue_size_, + napi_env env_, + void* finalize_data_, + napi_finalize finalize_cb_, + napi_threadsafe_function_call_js call_js_cb_): + AsyncResource(env_->isolate, + resource, + *v8::String::Utf8Value(env_->isolate, name)), + thread_count(thread_count_), + is_closing(false), + context(context_), + max_queue_size(max_queue_size_), + env(env_), + finalize_data(finalize_data_), + finalize_cb(finalize_cb_), + idle_running(false), + call_js_cb(call_js_cb_ == nullptr ? CallJs : call_js_cb_), + handles_closing(false) { + ref.Reset(env->isolate, func); + node::AddEnvironmentCleanupHook(env->isolate, Cleanup, this); + } + + ~TsFn() { + node::RemoveEnvironmentCleanupHook(env->isolate, Cleanup, this); + if (ref.IsEmpty()) + return; + ref.ClearWeak(); + ref.Reset(); + } + + // These methods can be called from any thread. + + napi_status Push(void* data, napi_threadsafe_function_call_mode mode) { + node::Mutex::ScopedLock lock(this->mutex); + + while (queue.size() >= max_queue_size && + max_queue_size > 0 && + !is_closing) { + if (mode == napi_tsfn_nonblocking) { + return napi_queue_full; + } + cond->Wait(lock); + } + + if (is_closing) { + if (thread_count == 0) { + return napi_invalid_arg; + } else { + thread_count--; + return napi_closing; + } + } else { + if (uv_async_send(&async) != 0) { + return napi_generic_failure; + } + queue.push(data); + return napi_ok; + } + } + + napi_status Acquire() { + node::Mutex::ScopedLock lock(this->mutex); + + if (is_closing) { + return napi_closing; + } + + thread_count++; + + return napi_ok; + } + + napi_status Release(napi_threadsafe_function_release_mode mode) { + node::Mutex::ScopedLock lock(this->mutex); + + if (thread_count == 0) { + return napi_invalid_arg; + } + + thread_count--; + + if (thread_count == 0 || mode == napi_tsfn_abort) { + if (!is_closing) { + is_closing = (mode == napi_tsfn_abort); + if (is_closing) { + cond->Signal(lock); + } + if (uv_async_send(&async) != 0) { + return napi_generic_failure; + } + } + } + + return napi_ok; + } + + void EmptyQueueAndDelete() { + for (; !queue.empty() ; queue.pop()) { + call_js_cb(nullptr, nullptr, context, queue.front()); + } + delete this; + } + + // These methods must only be called from the loop thread. + + napi_status Init() { + TsFn* ts_fn = this; + + if (uv_async_init(env->loop, &async, AsyncCb) == 0) { + if (max_queue_size > 0) { + cond.reset(new node::ConditionVariable); + } + if ((max_queue_size == 0 || cond.get() != nullptr) && + uv_idle_init(env->loop, &idle) == 0) { + return napi_ok; + } + + uv_close(reinterpret_cast(&async), + [] (uv_handle_t* handle) -> void { + TsFn* ts_fn = + node::ContainerOf(&TsFn::async, + reinterpret_cast(handle)); + delete ts_fn; + }); + + // Prevent the thread-safe function from being deleted here, because + // the callback above will delete it. + ts_fn = nullptr; + } + + delete ts_fn; + + return napi_generic_failure; + } + + napi_status Unref() { + uv_unref(reinterpret_cast(&async)); + uv_unref(reinterpret_cast(&idle)); + + return napi_ok; + } + + napi_status Ref() { + uv_ref(reinterpret_cast(&async)); + uv_ref(reinterpret_cast(&idle)); + + return napi_ok; + } + + void DispatchOne() { + void* data; + bool popped_value = false; + bool idle_stop_failed = false; + + { + node::Mutex::ScopedLock lock(this->mutex); + if (is_closing) { + CloseHandlesAndMaybeDelete(); + } else { + size_t size = queue.size(); + if (size > 0) { + data = queue.front(); + queue.pop(); + popped_value = true; + if (size == max_queue_size && max_queue_size > 0) { + cond->Signal(lock); + } + size--; + } + + if (size == 0) { + if (thread_count == 0) { + is_closing = true; + cond->Signal(lock); + CloseHandlesAndMaybeDelete(); + } else { + if (uv_idle_stop(&idle) != 0) { + idle_stop_failed = true; + } else { + idle_running = false; + } + } + } + } + } + + if (popped_value || idle_stop_failed) { + v8::HandleScope scope(env->isolate); + CallbackScope cb_scope(this); + + if (idle_stop_failed) { + CHECK(napi_throw_error(env, + "ERR_NAPI_TSFN_STOP_IDLE_LOOP", + "Failed to stop the idle loop") == napi_ok); + } else { + v8::Local js_cb = + v8::Local::New(env->isolate, ref); + call_js_cb(env, + v8impl::JsValueFromV8LocalValue(js_cb), + context, + data); + } + } + } + + node::Environment* NodeEnv() { + // For some reason grabbing the Node.js environment requires a handle scope. + v8::HandleScope scope(env->isolate); + return node::Environment::GetCurrent(env->isolate); + } + + void MaybeStartIdle() { + if (!idle_running) { + if (uv_idle_start(&idle, IdleCb) != 0) { + v8::HandleScope scope(env->isolate); + CallbackScope cb_scope(this); + CHECK(napi_throw_error(env, + "ERR_NAPI_TSFN_START_IDLE_LOOP", + "Failed to start the idle loop") == napi_ok); + } + } + } + + void Finalize() { + v8::HandleScope scope(env->isolate); + if (finalize_cb) { + CallbackScope cb_scope(this); + finalize_cb(env, finalize_data, context); + } + EmptyQueueAndDelete(); + } + + inline void* Context() { + return context; + } + + void CloseHandlesAndMaybeDelete(bool set_closing = false) { + if (set_closing) { + node::Mutex::ScopedLock lock(this->mutex); + is_closing = true; + cond->Signal(lock); + } + if (handles_closing) { + return; + } + handles_closing = true; + uv_close( + reinterpret_cast(&async), + [] (uv_handle_t* handle) -> void { + TsFn* ts_fn = node::ContainerOf(&TsFn::async, + reinterpret_cast(handle)); + uv_close( + reinterpret_cast(&ts_fn->idle), + [] (uv_handle_t* handle) -> void { + TsFn* ts_fn = node::ContainerOf(&TsFn::idle, + reinterpret_cast(handle)); + ts_fn->Finalize(); + }); + }); + } + + // Default way of calling into JavaScript. Used when TsFn is constructed + // without a call_js_cb_. + static void CallJs(napi_env env, napi_value cb, void* context, void* data) { + if (!(env == nullptr || cb == nullptr)) { + napi_value recv; + napi_status status; + + status = napi_get_undefined(env, &recv); + if (status != napi_ok) { + napi_throw_error(env, "ERR_NAPI_TSFN_GET_UNDEFINED", + "Failed to retrieve undefined value"); + return; + } + + status = napi_call_function(env, recv, cb, 0, nullptr, nullptr); + if (status != napi_ok && status != napi_pending_exception) { + napi_throw_error(env, "ERR_NAPI_TSFN_CALL_JS", + "Failed to call JS callback"); + return; + } + } + } + + static void IdleCb(uv_idle_t* idle) { + TsFn* ts_fn = + node::ContainerOf(&TsFn::idle, idle); + ts_fn->DispatchOne(); + } + + static void AsyncCb(uv_async_t* async) { + TsFn* ts_fn = + node::ContainerOf(&TsFn::async, async); + ts_fn->MaybeStartIdle(); + } + + static void Cleanup(void* data) { + reinterpret_cast(data)->CloseHandlesAndMaybeDelete(true); + } + + private: + // These are variables protected by the mutex. + node::Mutex mutex; + std::unique_ptr cond; + std::queue queue; + uv_async_t async; + uv_idle_t idle; + size_t thread_count; + bool is_closing; + + // These are variables set once, upon creation, and then never again, which + // means we don't need the mutex to read them. + void* context; + size_t max_queue_size; + + // These are variables accessed only from the loop thread. + v8::Persistent ref; + napi_env env; + void* finalize_data; + napi_finalize finalize_cb; + bool idle_running; + napi_async_context async_context; + napi_threadsafe_function_call_js call_js_cb; + bool handles_closing; +}; + +NAPI_EXTERN napi_status +napi_create_threadsafe_function(napi_env env, + napi_value func, + napi_value async_resource, + napi_value async_resource_name, + size_t max_queue_size, + size_t initial_thread_count, + void* thread_finalize_data, + napi_finalize thread_finalize_cb, + void* context, + napi_threadsafe_function_call_js call_js_cb, + napi_threadsafe_function* result) { + CHECK_ENV(env); + CHECK_ARG(env, func); + CHECK_ARG(env, async_resource_name); + RETURN_STATUS_IF_FALSE(env, initial_thread_count > 0, napi_invalid_arg); + CHECK_ARG(env, result); + + napi_status status = napi_ok; + + v8::Local v8_func; + CHECK_TO_FUNCTION(env, v8_func, func); + + v8::Local v8_context = env->isolate->GetCurrentContext(); + + v8::Local v8_resource; + if (async_resource == nullptr) { + v8_resource = v8::Object::New(env->isolate); + } else { + CHECK_TO_OBJECT(env, v8_context, v8_resource, async_resource); + } + + v8::Local v8_name; + CHECK_TO_STRING(env, v8_context, v8_name, async_resource_name); + + TsFn* ts_fn = new TsFn(v8_func, + v8_resource, + v8_name, + initial_thread_count, + context, + max_queue_size, + env, + thread_finalize_data, + thread_finalize_cb, + call_js_cb); + + if (ts_fn == nullptr) { + status = napi_generic_failure; + } else { + // Init deletes ts_fn upon failure. + status = ts_fn->Init(); + if (status == napi_ok) { + *result = reinterpret_cast(ts_fn); + } + } + + return napi_set_last_error(env, status); +} + +NAPI_EXTERN napi_status +napi_get_threadsafe_function_context(napi_threadsafe_function func, + void** result) { + CHECK(func != nullptr); + CHECK(result != nullptr); + + *result = reinterpret_cast(func)->Context(); + return napi_ok; +} + +NAPI_EXTERN napi_status +napi_call_threadsafe_function(napi_threadsafe_function func, + void* data, + napi_threadsafe_function_call_mode is_blocking) { + CHECK(func != nullptr); + return reinterpret_cast(func)->Push(data, is_blocking); +} + +NAPI_EXTERN napi_status +napi_acquire_threadsafe_function(napi_threadsafe_function func) { + CHECK(func != nullptr); + return reinterpret_cast(func)->Acquire(); +} + +NAPI_EXTERN napi_status +napi_release_threadsafe_function(napi_threadsafe_function func, + napi_threadsafe_function_release_mode mode) { + CHECK(func != nullptr); + return reinterpret_cast(func)->Release(mode); +} + +NAPI_EXTERN napi_status +napi_unref_threadsafe_function(napi_env env, napi_threadsafe_function func) { + CHECK(func != nullptr); + return reinterpret_cast(func)->Unref(); +} + +NAPI_EXTERN napi_status +napi_ref_threadsafe_function(napi_env env, napi_threadsafe_function func) { + CHECK(func != nullptr); + return reinterpret_cast(func)->Ref(); +} diff --git a/src/node_api.h b/src/node_api.h index 6e6c575b76609a..0868eabc5c6961 100644 --- a/src/node_api.h +++ b/src/node_api.h @@ -595,6 +595,44 @@ NAPI_EXTERN napi_status napi_run_script(napi_env env, NAPI_EXTERN napi_status napi_get_uv_event_loop(napi_env env, struct uv_loop_s** loop); +#ifdef NAPI_EXPERIMENTAL +// Calling into JS from other threads +NAPI_EXTERN napi_status +napi_create_threadsafe_function(napi_env env, + napi_value func, + napi_value async_resource, + napi_value async_resource_name, + size_t max_queue_size, + size_t initial_thread_count, + void* thread_finalize_data, + napi_finalize thread_finalize_cb, + void* context, + napi_threadsafe_function_call_js call_js_cb, + napi_threadsafe_function* result); + +NAPI_EXTERN napi_status +napi_get_threadsafe_function_context(napi_threadsafe_function func, + void** result); + +NAPI_EXTERN napi_status +napi_call_threadsafe_function(napi_threadsafe_function func, + void* data, + napi_threadsafe_function_call_mode is_blocking); + +NAPI_EXTERN napi_status +napi_acquire_threadsafe_function(napi_threadsafe_function func); + +NAPI_EXTERN napi_status +napi_release_threadsafe_function(napi_threadsafe_function func, + napi_threadsafe_function_release_mode mode); + +NAPI_EXTERN napi_status +napi_unref_threadsafe_function(napi_env env, napi_threadsafe_function func); + +NAPI_EXTERN napi_status +napi_ref_threadsafe_function(napi_env env, napi_threadsafe_function func); + +#endif // NAPI_EXPERIMENTAL EXTERN_C_END #endif // SRC_NODE_API_H_ diff --git a/src/node_api_types.h b/src/node_api_types.h index 76f38802e83e2e..cfc2b879ab31ef 100644 --- a/src/node_api_types.h +++ b/src/node_api_types.h @@ -20,6 +20,9 @@ typedef struct napi_callback_info__ *napi_callback_info; typedef struct napi_async_context__ *napi_async_context; typedef struct napi_async_work__ *napi_async_work; typedef struct napi_deferred__ *napi_deferred; +#ifdef NAPI_EXPERIMENTAL +typedef struct napi_threadsafe_function__* napi_threadsafe_function; +#endif // NAPI_EXPERIMENTAL typedef enum { napi_default = 0, @@ -72,9 +75,25 @@ typedef enum { napi_cancelled, napi_escape_called_twice, napi_handle_scope_mismatch, - napi_callback_scope_mismatch + napi_callback_scope_mismatch, +#ifdef NAPI_EXPERIMENTAL + napi_queue_full, + napi_closing, +#endif // NAPI_EXPERIMENTAL } napi_status; +#ifdef NAPI_EXPERIMENTAL +typedef enum { + napi_tsfn_release, + napi_tsfn_abort +} napi_threadsafe_function_release_mode; + +typedef enum { + napi_tsfn_nonblocking, + napi_tsfn_blocking +} napi_threadsafe_function_call_mode; +#endif // NAPI_EXPERIMENTAL + typedef napi_value (*napi_callback)(napi_env env, napi_callback_info info); typedef void (*napi_finalize)(napi_env env, @@ -86,6 +105,13 @@ typedef void (*napi_async_complete_callback)(napi_env env, napi_status status, void* data); +#ifdef NAPI_EXPERIMENTAL +typedef void (*napi_threadsafe_function_call_js)(napi_env env, + napi_value js_callback, + void* context, + void* data); +#endif // NAPI_EXPERIMENTAL + typedef struct { // One of utf8name or name should be NULL. const char* utf8name; diff --git a/test/addons-napi/test_threadsafe_function/binding.c b/test/addons-napi/test_threadsafe_function/binding.c new file mode 100644 index 00000000000000..551705b1f21074 --- /dev/null +++ b/test/addons-napi/test_threadsafe_function/binding.c @@ -0,0 +1,254 @@ +// For the purpose of this test we use libuv's threading library. When deciding +// on a threading library for a new project it bears remembering that in the +// future libuv may introduce API changes which may render it non-ABI-stable, +// which, in turn, may affect the ABI stability of the project despite its use +// of N-API. +#include +#define NAPI_EXPERIMENTAL +#include +#include "../common.h" + +#define ARRAY_LENGTH 10 + +static uv_thread_t uv_threads[2]; +static napi_threadsafe_function ts_fn; + +typedef struct { + napi_threadsafe_function_call_mode block_on_full; + napi_threadsafe_function_release_mode abort; + bool start_secondary; + napi_ref js_finalize_cb; +} ts_fn_hint; + +static ts_fn_hint ts_info; + +// Thread data to transmit to JS +static int ints[ARRAY_LENGTH]; + +static void secondary_thread(void* data) { + napi_threadsafe_function ts_fn = data; + + if (napi_release_threadsafe_function(ts_fn, napi_tsfn_release) != napi_ok) { + napi_fatal_error("secondary_thread", NAPI_AUTO_LENGTH, + "napi_release_threadsafe_function failed", NAPI_AUTO_LENGTH); + } +} + +// Source thread producing the data +static void data_source_thread(void* data) { + napi_threadsafe_function ts_fn = data; + int index; + void* hint; + ts_fn_hint *ts_fn_info; + napi_status status; + bool queue_was_full = false; + bool queue_was_closing = false; + + if (napi_get_threadsafe_function_context(ts_fn, &hint) != napi_ok) { + napi_fatal_error("data_source_thread", NAPI_AUTO_LENGTH, + "napi_get_threadsafe_function_context failed", NAPI_AUTO_LENGTH); + } + + ts_fn_info = (ts_fn_hint *)hint; + + if (ts_fn_info != &ts_info) { + napi_fatal_error("data_source_thread", NAPI_AUTO_LENGTH, + "thread-safe function hint is not as expected", NAPI_AUTO_LENGTH); + } + + if (ts_fn_info->start_secondary) { + if (napi_acquire_threadsafe_function(ts_fn) != napi_ok) { + napi_fatal_error("data_source_thread", NAPI_AUTO_LENGTH, + "napi_acquire_threadsafe_function failed", NAPI_AUTO_LENGTH); + } + + if (uv_thread_create(&uv_threads[1], secondary_thread, ts_fn) != 0) { + napi_fatal_error("data_source_thread", NAPI_AUTO_LENGTH, + "failed to start secondary thread", NAPI_AUTO_LENGTH); + } + } + + for (index = ARRAY_LENGTH - 1; index > -1 && !queue_was_closing; index--) { + status = napi_call_threadsafe_function(ts_fn, &ints[index], + ts_fn_info->block_on_full); + switch (status) { + case napi_queue_full: + queue_was_full = true; + index++; + // fall through + + case napi_ok: + continue; + + case napi_closing: + queue_was_closing = true; + break; + + default: + napi_fatal_error("data_source_thread", NAPI_AUTO_LENGTH, + "napi_call_threadsafe_function failed", NAPI_AUTO_LENGTH); + } + } + + // Assert that the enqueuing of a value was refused at least once, if this is + // a non-blocking test run. + if (!ts_fn_info->block_on_full && !queue_was_full) { + napi_fatal_error("data_source_thread", NAPI_AUTO_LENGTH, + "queue was never full", NAPI_AUTO_LENGTH); + } + + // Assert that the queue was marked as closing at least once, if this is an + // aborting test run. + if (ts_fn_info->abort == napi_tsfn_abort && !queue_was_closing) { + napi_fatal_error("data_source_thread", NAPI_AUTO_LENGTH, + "queue was never closing", NAPI_AUTO_LENGTH); + } + + if (!queue_was_closing && + napi_release_threadsafe_function(ts_fn, napi_tsfn_release) != napi_ok) { + napi_fatal_error("data_source_thread", NAPI_AUTO_LENGTH, + "napi_release_threadsafe_function failed", NAPI_AUTO_LENGTH); + } +} + +// Getting the data into JS +static void call_js(napi_env env, napi_value cb, void* hint, void* data) { + if (!(env == NULL || cb == NULL)) { + napi_value argv, undefined; + NAPI_CALL_RETURN_VOID(env, napi_create_int32(env, *(int*)data, &argv)); + NAPI_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined)); + NAPI_CALL_RETURN_VOID(env, napi_call_function(env, undefined, cb, 1, &argv, + NULL)); + } +} + +// Cleanup +static napi_value StopThread(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value argv[2]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL)); + napi_valuetype value_type; + NAPI_CALL(env, napi_typeof(env, argv[0], &value_type)); + NAPI_ASSERT(env, value_type == napi_function, + "StopThread argument is a function"); + NAPI_ASSERT(env, (ts_fn != NULL), "Existing threadsafe function"); + NAPI_CALL(env, + napi_create_reference(env, argv[0], 1, &(ts_info.js_finalize_cb))); + bool abort; + NAPI_CALL(env, napi_get_value_bool(env, argv[1], &abort)); + NAPI_CALL(env, + napi_release_threadsafe_function(ts_fn, + abort ? napi_tsfn_abort : napi_tsfn_release)); + ts_fn = NULL; + return NULL; +} + +// Join the thread and inform JS that we're done. +static void join_the_threads(napi_env env, void *data, void *hint) { + uv_thread_t *the_threads = data; + ts_fn_hint *the_hint = hint; + napi_value js_cb, undefined; + + uv_thread_join(&the_threads[0]); + if (the_hint->start_secondary) { + uv_thread_join(&the_threads[1]); + } + + NAPI_CALL_RETURN_VOID(env, + napi_get_reference_value(env, the_hint->js_finalize_cb, &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, + the_hint->js_finalize_cb)); +} + +static napi_value StartThreadInternal(napi_env env, + napi_callback_info info, + napi_threadsafe_function_call_js cb, + bool block_on_full) { + size_t argc = 3; + napi_value argv[3]; + + ts_info.block_on_full = + (block_on_full ? napi_tsfn_blocking : napi_tsfn_nonblocking); + + NAPI_ASSERT(env, (ts_fn == NULL), "Existing thread-safe function"); + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL)); + napi_value async_name; + NAPI_CALL(env, napi_create_string_utf8(env, "N-API Thread-safe Function Test", + NAPI_AUTO_LENGTH, &async_name)); + NAPI_CALL(env, napi_create_threadsafe_function(env, argv[0], NULL, async_name, + 2, 2, uv_threads, join_the_threads, &ts_info, cb, &ts_fn)); + bool abort; + NAPI_CALL(env, napi_get_value_bool(env, argv[1], &abort)); + ts_info.abort = abort ? napi_tsfn_abort : napi_tsfn_release; + NAPI_CALL(env, napi_get_value_bool(env, argv[2], &(ts_info.start_secondary))); + + NAPI_ASSERT(env, + (uv_thread_create(&uv_threads[0], data_source_thread, ts_fn) == 0), + "Thread creation"); + + return NULL; +} + +static napi_value Unref(napi_env env, napi_callback_info info) { + NAPI_ASSERT(env, ts_fn != NULL, "No existing thread-safe function"); + NAPI_CALL(env, napi_unref_threadsafe_function(env, ts_fn)); + return NULL; +} + +static napi_value Release(napi_env env, napi_callback_info info) { + NAPI_ASSERT(env, ts_fn != NULL, "No existing thread-safe function"); + NAPI_CALL(env, napi_release_threadsafe_function(ts_fn, napi_tsfn_release)); + return NULL; +} + +// Startup +static napi_value StartThread(napi_env env, napi_callback_info info) { + return StartThreadInternal(env, info, call_js, true); +} + +static napi_value StartThreadNonblocking(napi_env env, + napi_callback_info info) { + return StartThreadInternal(env, info, call_js, false); +} + +static napi_value StartThreadNoNative(napi_env env, napi_callback_info info) { + return StartThreadInternal(env, info, NULL, true); +} + +// Module init +static napi_value Init(napi_env env, napi_value exports) { + size_t index; + for (index = 0; index < ARRAY_LENGTH; index++) { + ints[index] = index; + } + napi_value js_array_length; + napi_create_uint32(env, ARRAY_LENGTH, &js_array_length); + + napi_property_descriptor properties[] = { + { + "ARRAY_LENGTH", + NULL, + NULL, + NULL, + NULL, + js_array_length, + napi_enumerable, + NULL + }, + DECLARE_NAPI_PROPERTY("StartThread", StartThread), + DECLARE_NAPI_PROPERTY("StartThreadNoNative", StartThreadNoNative), + DECLARE_NAPI_PROPERTY("StartThreadNonblocking", StartThreadNonblocking), + DECLARE_NAPI_PROPERTY("StopThread", StopThread), + DECLARE_NAPI_PROPERTY("Unref", Unref), + DECLARE_NAPI_PROPERTY("Release", Release), + }; + + NAPI_CALL(env, napi_define_properties(env, exports, + sizeof(properties)/sizeof(properties[0]), properties)); + + return exports; +} +NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/test/addons-napi/test_threadsafe_function/binding.gyp b/test/addons-napi/test_threadsafe_function/binding.gyp new file mode 100644 index 00000000000000..b60352e05af103 --- /dev/null +++ b/test/addons-napi/test_threadsafe_function/binding.gyp @@ -0,0 +1,8 @@ +{ + 'targets': [ + { + 'target_name': 'binding', + 'sources': ['binding.c'] + } + ] +} diff --git a/test/addons-napi/test_threadsafe_function/test.js b/test/addons-napi/test_threadsafe_function/test.js new file mode 100644 index 00000000000000..8d8a6d9d8c6827 --- /dev/null +++ b/test/addons-napi/test_threadsafe_function/test.js @@ -0,0 +1,166 @@ +'use strict'; + +const common = require('../../common'); +const assert = require('assert'); +const binding = require(`./build/${common.buildType}/binding`); +const { fork } = require('child_process'); +const expectedArray = (function(arrayLength) { + const result = []; + for (let index = 0; index < arrayLength; index++) { + result.push(arrayLength - 1 - index); + } + return result; +})(binding.ARRAY_LENGTH); + +common.crashOnUnhandledRejection(); + +// Handle the rapid teardown test case as the child process. We unref the +// thread-safe function after we have received two values. This causes the +// process to exit and the environment cleanup handler to be invoked. +if (process.argv[2] === 'child') { + let callCount = 0; + binding.StartThread((value) => { + callCount++; + console.log(value); + if (callCount === 2) { + binding.Unref(); + } + }, false /* abort */, true /* launchSecondary */); + + // Release the thread-safe function from the main thread so that it may be + // torn down via the environment cleanup handler. + binding.Release(); + return; +} + +function testWithJSMarshaller({ + threadStarter, + quitAfter, + abort, + launchSecondary }) { + return new Promise((resolve) => { + const array = []; + binding[threadStarter](function testCallback(value) { + array.push(value); + if (array.length === quitAfter) { + setImmediate(() => { + binding.StopThread(common.mustCall(() => { + resolve(array); + }), !!abort); + }); + } + }, !!abort, !!launchSecondary); + if (threadStarter === 'StartThreadNonblocking') { + // Let's make this thread really busy for a short while to ensure that + // the queue fills and the thread receives a napi_queue_full. + const start = Date.now(); + while (Date.now() - start < 200); + } + }); +} + +new Promise(function testWithoutJSMarshaller(resolve) { + let callCount = 0; + binding.StartThreadNoNative(function testCallback() { + callCount++; + + // The default call-into-JS implementation passes no arguments. + assert.strictEqual(arguments.length, 0); + if (callCount === binding.ARRAY_LENGTH) { + setImmediate(() => { + binding.StopThread(common.mustCall(() => { + resolve(); + }), false); + }); + } + }, false /* abort */, false /* launchSecondary */); +}) + +// Start the thread in blocking mode, and assert that all values are passed. +// Quit after it's done. +.then(() => testWithJSMarshaller({ + threadStarter: 'StartThread', + quitAfter: binding.ARRAY_LENGTH +})) +.then((result) => assert.deepStrictEqual(result, expectedArray)) + +// Start the thread in non-blocking mode, and assert that all values are passed. +// Quit after it's done. +.then(() => testWithJSMarshaller({ + threadStarter: 'StartThreadNonblocking', + quitAfter: binding.ARRAY_LENGTH +})) +.then((result) => assert.deepStrictEqual(result, expectedArray)) + +// Start the thread in blocking mode, and assert that all values are passed. +// Quit early, but let the thread finish. +.then(() => testWithJSMarshaller({ + threadStarter: 'StartThread', + quitAfter: 1 +})) +.then((result) => assert.deepStrictEqual(result, expectedArray)) + +// Start the thread in non-blocking mode, and assert that all values are passed. +// Quit early, but let the thread finish. +.then(() => testWithJSMarshaller({ + threadStarter: 'StartThreadNonblocking', + quitAfter: 1 +})) +.then((result) => assert.deepStrictEqual(result, expectedArray)) + +// Start the thread in blocking mode, and assert that all values are passed. +// Quit early, but let the thread finish. Launch a secondary thread to test the +// reference counter incrementing functionality. +.then(() => testWithJSMarshaller({ + threadStarter: 'StartThread', + quitAfter: 1, + launchSecondary: true +})) +.then((result) => assert.deepStrictEqual(result, expectedArray)) + +// Start the thread in non-blocking mode, and assert that all values are passed. +// Quit early, but let the thread finish. Launch a secondary thread to test the +// reference counter incrementing functionality. +.then(() => testWithJSMarshaller({ + threadStarter: 'StartThreadNonblocking', + quitAfter: 1, + launchSecondary: true +})) +.then((result) => assert.deepStrictEqual(result, expectedArray)) + +// Start the thread in blocking mode, and assert that it could not finish. +// Quit early and aborting. +.then(() => testWithJSMarshaller({ + threadStarter: 'StartThread', + quitAfter: 1, + abort: true +})) +.then((result) => assert.strictEqual(result.indexOf(0), -1)) + +// Start the thread in non-blocking mode, and assert that it could not finish. +// Quit early and aborting. +.then(() => testWithJSMarshaller({ + threadStarter: 'StartThreadNonblocking', + quitAfter: 1, + abort: true +})) +.then((result) => assert.strictEqual(result.indexOf(0), -1)) + +// Start a child process to test rapid teardown +.then(() => { + return new Promise((resolve, reject) => { + let output = ''; + const child = fork(__filename, ['child'], { + stdio: [process.stdin, 'pipe', process.stderr, 'ipc'] + }); + child.on('close', (code) => { + if (code === 0) { + resolve(output.match(/\S+/g)); + } else { + reject(new Error('Child process died with code ' + code)); + } + }); + child.stdout.on('data', (data) => (output += data.toString())); + }); +}) +.then((result) => assert.strictEqual(result.indexOf(0), -1)); From c5a11dc58e48775dc3c510fed7fc858d54bcbd3a Mon Sep 17 00:00:00 2001 From: cjihrig Date: Fri, 29 Jun 2018 19:52:46 -0400 Subject: [PATCH 05/34] n-api: fix compiler warning private field 'async_context' is not used [-Wunused-private-field] PR-URL: https://github.com/nodejs/node/pull/21597 Refs: https://github.com/nodejs/node/pull/17887 Backport-PR-URL: https://github.com/nodejs/node/pull/25002 Reviewed-By: Gabriel Schulhof Reviewed-By: Daniel Bevenius --- src/node_api.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/node_api.cc b/src/node_api.cc index 20fb2f8c9a0af8..e9d30eeef6454e 100644 --- a/src/node_api.cc +++ b/src/node_api.cc @@ -3920,7 +3920,6 @@ class TsFn: public node::AsyncResource { void* finalize_data; napi_finalize finalize_cb; bool idle_running; - napi_async_context async_context; napi_threadsafe_function_call_js call_js_cb; bool handles_closing; }; From 367505940ab09369262295cf21d3ebe08b943ad5 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Wed, 18 Jul 2018 09:33:51 -0400 Subject: [PATCH 06/34] n-api: guard against cond null dereference A condition variable is only created by the thread-safe function if the queue size is set to something larger than zero. This adds null-checks around the condition variable and tests for the case where the queue size is zero. Fixes: https://github.com/nodejs/help/issues/1387 PR-URL: https://github.com/nodejs/node/pull/21871 Backport-PR-URL: https://github.com/nodejs/node/pull/25002 Reviewed-By: Benjamin Gruenbaum Reviewed-By: Colin Ihrig Reviewed-By: Michael Dawson --- src/node_api.cc | 10 ++- .../test_threadsafe_function/binding.c | 39 +++++++-- .../test_threadsafe_function/test.js | 84 ++++++++++++++----- 3 files changed, 104 insertions(+), 29 deletions(-) diff --git a/src/node_api.cc b/src/node_api.cc index e9d30eeef6454e..4628ce079f4513 100644 --- a/src/node_api.cc +++ b/src/node_api.cc @@ -3683,7 +3683,7 @@ class TsFn: public node::AsyncResource { if (thread_count == 0 || mode == napi_tsfn_abort) { if (!is_closing) { is_closing = (mode == napi_tsfn_abort); - if (is_closing) { + if (is_closing && max_queue_size > 0) { cond->Signal(lock); } if (uv_async_send(&async) != 0) { @@ -3772,7 +3772,9 @@ class TsFn: public node::AsyncResource { if (size == 0) { if (thread_count == 0) { is_closing = true; - cond->Signal(lock); + if (max_queue_size > 0) { + cond->Signal(lock); + } CloseHandlesAndMaybeDelete(); } else { if (uv_idle_stop(&idle) != 0) { @@ -3839,7 +3841,9 @@ class TsFn: public node::AsyncResource { if (set_closing) { node::Mutex::ScopedLock lock(this->mutex); is_closing = true; - cond->Signal(lock); + if (max_queue_size > 0) { + cond->Signal(lock); + } } if (handles_closing) { return; diff --git a/test/addons-napi/test_threadsafe_function/binding.c b/test/addons-napi/test_threadsafe_function/binding.c index 551705b1f21074..354012a288b3ca 100644 --- a/test/addons-napi/test_threadsafe_function/binding.c +++ b/test/addons-napi/test_threadsafe_function/binding.c @@ -9,6 +9,7 @@ #include "../common.h" #define ARRAY_LENGTH 10 +#define MAX_QUEUE_SIZE 2 static uv_thread_t uv_threads[2]; static napi_threadsafe_function ts_fn; @@ -18,6 +19,7 @@ typedef struct { napi_threadsafe_function_release_mode abort; bool start_secondary; napi_ref js_finalize_cb; + uint32_t max_queue_size; } ts_fn_hint; static ts_fn_hint ts_info; @@ -71,6 +73,12 @@ static void data_source_thread(void* data) { for (index = ARRAY_LENGTH - 1; index > -1 && !queue_was_closing; index--) { status = napi_call_threadsafe_function(ts_fn, &ints[index], ts_fn_info->block_on_full); + if (ts_fn_info->max_queue_size == 0) { + // Let's make this thread really busy for 200 ms to give the main thread a + // chance to abort. + uint64_t start = uv_hrtime(); + for (; uv_hrtime() - start < 200000000;); + } switch (status) { case napi_queue_full: queue_was_full = true; @@ -167,8 +175,8 @@ static napi_value StartThreadInternal(napi_env env, napi_callback_info info, napi_threadsafe_function_call_js cb, bool block_on_full) { - size_t argc = 3; - napi_value argv[3]; + size_t argc = 4; + napi_value argv[4]; ts_info.block_on_full = (block_on_full ? napi_tsfn_blocking : napi_tsfn_nonblocking); @@ -178,8 +186,18 @@ static napi_value StartThreadInternal(napi_env env, napi_value async_name; NAPI_CALL(env, napi_create_string_utf8(env, "N-API Thread-safe Function Test", NAPI_AUTO_LENGTH, &async_name)); - NAPI_CALL(env, napi_create_threadsafe_function(env, argv[0], NULL, async_name, - 2, 2, uv_threads, join_the_threads, &ts_info, cb, &ts_fn)); + NAPI_CALL(env, napi_get_value_uint32(env, argv[3], &ts_info.max_queue_size)); + NAPI_CALL(env, napi_create_threadsafe_function(env, + argv[0], + NULL, + async_name, + ts_info.max_queue_size, + 2, + uv_threads, + join_the_threads, + &ts_info, + cb, + &ts_fn)); bool abort; NAPI_CALL(env, napi_get_value_bool(env, argv[1], &abort)); ts_info.abort = abort ? napi_tsfn_abort : napi_tsfn_release; @@ -224,8 +242,9 @@ static napi_value Init(napi_env env, napi_value exports) { for (index = 0; index < ARRAY_LENGTH; index++) { ints[index] = index; } - napi_value js_array_length; + napi_value js_array_length, js_max_queue_size; napi_create_uint32(env, ARRAY_LENGTH, &js_array_length); + napi_create_uint32(env, MAX_QUEUE_SIZE, &js_max_queue_size); napi_property_descriptor properties[] = { { @@ -238,6 +257,16 @@ static napi_value Init(napi_env env, napi_value exports) { napi_enumerable, NULL }, + { + "MAX_QUEUE_SIZE", + NULL, + NULL, + NULL, + NULL, + js_max_queue_size, + napi_enumerable, + NULL + }, DECLARE_NAPI_PROPERTY("StartThread", StartThread), DECLARE_NAPI_PROPERTY("StartThreadNoNative", StartThreadNoNative), DECLARE_NAPI_PROPERTY("StartThreadNonblocking", StartThreadNonblocking), diff --git a/test/addons-napi/test_threadsafe_function/test.js b/test/addons-napi/test_threadsafe_function/test.js index 8d8a6d9d8c6827..ac2549a8c08e5c 100644 --- a/test/addons-napi/test_threadsafe_function/test.js +++ b/test/addons-napi/test_threadsafe_function/test.js @@ -25,7 +25,7 @@ if (process.argv[2] === 'child') { if (callCount === 2) { binding.Unref(); } - }, false /* abort */, true /* launchSecondary */); + }, false /* abort */, true /* launchSecondary */, +process.argv[3]); // Release the thread-safe function from the main thread so that it may be // torn down via the environment cleanup handler. @@ -37,6 +37,7 @@ function testWithJSMarshaller({ threadStarter, quitAfter, abort, + maxQueueSize, launchSecondary }) { return new Promise((resolve) => { const array = []; @@ -49,7 +50,7 @@ function testWithJSMarshaller({ }), !!abort); }); } - }, !!abort, !!launchSecondary); + }, !!abort, !!launchSecondary, maxQueueSize); if (threadStarter === 'StartThreadNonblocking') { // Let's make this thread really busy for a short while to ensure that // the queue fills and the thread receives a napi_queue_full. @@ -59,6 +60,24 @@ function testWithJSMarshaller({ }); } +function testUnref(queueSize) { + return new Promise((resolve, reject) => { + let output = ''; + const child = fork(__filename, ['child', queueSize], { + stdio: [process.stdin, 'pipe', process.stderr, 'ipc'] + }); + child.on('close', (code) => { + if (code === 0) { + resolve(output.match(/\S+/g)); + } else { + reject(new Error('Child process died with code ' + code)); + } + }); + child.stdout.on('data', (data) => (output += data.toString())); + }) + .then((result) => assert.strictEqual(result.indexOf(0), -1)); +} + new Promise(function testWithoutJSMarshaller(resolve) { let callCount = 0; binding.StartThreadNoNative(function testCallback() { @@ -73,13 +92,23 @@ new Promise(function testWithoutJSMarshaller(resolve) { }), false); }); } - }, false /* abort */, false /* launchSecondary */); + }, false /* abort */, false /* launchSecondary */, binding.MAX_QUEUE_SIZE); }) // Start the thread in blocking mode, and assert that all values are passed. // Quit after it's done. .then(() => testWithJSMarshaller({ threadStarter: 'StartThread', + maxQueueSize: binding.MAX_QUEUE_SIZE, + quitAfter: binding.ARRAY_LENGTH +})) +.then((result) => assert.deepStrictEqual(result, expectedArray)) + +// Start the thread in blocking mode with an infinite queue, and assert that all +// values are passed. Quit after it's done. +.then(() => testWithJSMarshaller({ + threadStarter: 'StartThread', + maxQueueSize: 0, quitAfter: binding.ARRAY_LENGTH })) .then((result) => assert.deepStrictEqual(result, expectedArray)) @@ -88,6 +117,7 @@ new Promise(function testWithoutJSMarshaller(resolve) { // Quit after it's done. .then(() => testWithJSMarshaller({ threadStarter: 'StartThreadNonblocking', + maxQueueSize: binding.MAX_QUEUE_SIZE, quitAfter: binding.ARRAY_LENGTH })) .then((result) => assert.deepStrictEqual(result, expectedArray)) @@ -96,6 +126,16 @@ new Promise(function testWithoutJSMarshaller(resolve) { // Quit early, but let the thread finish. .then(() => testWithJSMarshaller({ threadStarter: 'StartThread', + maxQueueSize: binding.MAX_QUEUE_SIZE, + quitAfter: 1 +})) +.then((result) => assert.deepStrictEqual(result, expectedArray)) + +// Start the thread in blocking mode with an infinite queue, and assert that all +// values are passed. Quit early, but let the thread finish. +.then(() => testWithJSMarshaller({ + threadStarter: 'StartThread', + maxQueueSize: 0, quitAfter: 1 })) .then((result) => assert.deepStrictEqual(result, expectedArray)) @@ -104,6 +144,7 @@ new Promise(function testWithoutJSMarshaller(resolve) { // Quit early, but let the thread finish. .then(() => testWithJSMarshaller({ threadStarter: 'StartThreadNonblocking', + maxQueueSize: binding.MAX_QUEUE_SIZE, quitAfter: 1 })) .then((result) => assert.deepStrictEqual(result, expectedArray)) @@ -114,6 +155,7 @@ new Promise(function testWithoutJSMarshaller(resolve) { .then(() => testWithJSMarshaller({ threadStarter: 'StartThread', quitAfter: 1, + maxQueueSize: binding.MAX_QUEUE_SIZE, launchSecondary: true })) .then((result) => assert.deepStrictEqual(result, expectedArray)) @@ -124,15 +166,27 @@ new Promise(function testWithoutJSMarshaller(resolve) { .then(() => testWithJSMarshaller({ threadStarter: 'StartThreadNonblocking', quitAfter: 1, + maxQueueSize: binding.MAX_QUEUE_SIZE, launchSecondary: true })) .then((result) => assert.deepStrictEqual(result, expectedArray)) // Start the thread in blocking mode, and assert that it could not finish. -// Quit early and aborting. +// Quit early by aborting. +.then(() => testWithJSMarshaller({ + threadStarter: 'StartThread', + quitAfter: 1, + maxQueueSize: binding.MAX_QUEUE_SIZE, + abort: true +})) +.then((result) => assert.strictEqual(result.indexOf(0), -1)) + +// Start the thread in blocking mode with an infinite queue, and assert that it +// could not finish. Quit early by aborting. .then(() => testWithJSMarshaller({ threadStarter: 'StartThread', quitAfter: 1, + maxQueueSize: 0, abort: true })) .then((result) => assert.strictEqual(result.indexOf(0), -1)) @@ -142,25 +196,13 @@ new Promise(function testWithoutJSMarshaller(resolve) { .then(() => testWithJSMarshaller({ threadStarter: 'StartThreadNonblocking', quitAfter: 1, + maxQueueSize: binding.MAX_QUEUE_SIZE, abort: true })) .then((result) => assert.strictEqual(result.indexOf(0), -1)) // Start a child process to test rapid teardown -.then(() => { - return new Promise((resolve, reject) => { - let output = ''; - const child = fork(__filename, ['child'], { - stdio: [process.stdin, 'pipe', process.stderr, 'ipc'] - }); - child.on('close', (code) => { - if (code === 0) { - resolve(output.match(/\S+/g)); - } else { - reject(new Error('Child process died with code ' + code)); - } - }); - child.stdout.on('data', (data) => (output += data.toString())); - }); -}) -.then((result) => assert.strictEqual(result.indexOf(0), -1)); +.then(() => testUnref(binding.MAX_QUEUE_SIZE)) + +// Start a child process with an infinite queue to test rapid teardown +.then(() => testUnref(0)); From 67b6e0d19ca2ca1fd0705f22730f1fd018568b5c Mon Sep 17 00:00:00 2001 From: Michael Dawson Date: Thu, 19 Jul 2018 18:15:01 -0400 Subject: [PATCH 07/34] src: fix may be uninitialized warning in n-api Backport-PR-URL: https://github.com/nodejs/node/pull/25002 PR-URL: https://github.com/nodejs/node/pull/21898 Reviewed-By: Anna Henningsen Reviewed-By: Colin Ihrig Reviewed-By: James M Snell Reviewed-By: Trivikram Kamat Reviewed-By: Jon Moss Reviewed-By: Luigi Pinca --- src/node_api.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node_api.cc b/src/node_api.cc index 4628ce079f4513..2421b9c333dbeb 100644 --- a/src/node_api.cc +++ b/src/node_api.cc @@ -3749,7 +3749,7 @@ class TsFn: public node::AsyncResource { } void DispatchOne() { - void* data; + void* data = nullptr; bool popped_value = false; bool idle_stop_failed = false; From 09b88aabb3fd5cfd2da98ae83c2f1edd82d899c5 Mon Sep 17 00:00:00 2001 From: Lars-Magnus Skog Date: Fri, 27 Jul 2018 16:17:32 +0200 Subject: [PATCH 08/34] n-api: remove idle_running from TsFn The idle_running member variable in TsFn is always false and can therefore be removed. Backport-PR-URL: https://github.com/nodejs/node/pull/25002 PR-URL: https://github.com/nodejs/node/pull/22520 Reviewed-By: James M Snell Reviewed-By: Anna Henningsen Reviewed-By: Gabriel Schulhof Reviewed-By: Gus Caplan --- src/node_api.cc | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/node_api.cc b/src/node_api.cc index 2421b9c333dbeb..89a51162d0ff88 100644 --- a/src/node_api.cc +++ b/src/node_api.cc @@ -3614,7 +3614,6 @@ class TsFn: public node::AsyncResource { env(env_), finalize_data(finalize_data_), finalize_cb(finalize_cb_), - idle_running(false), call_js_cb(call_js_cb_ == nullptr ? CallJs : call_js_cb_), handles_closing(false) { ref.Reset(env->isolate, func); @@ -3779,8 +3778,6 @@ class TsFn: public node::AsyncResource { } else { if (uv_idle_stop(&idle) != 0) { idle_stop_failed = true; - } else { - idle_running = false; } } } @@ -3813,14 +3810,12 @@ class TsFn: public node::AsyncResource { } void MaybeStartIdle() { - if (!idle_running) { - if (uv_idle_start(&idle, IdleCb) != 0) { - v8::HandleScope scope(env->isolate); - CallbackScope cb_scope(this); - CHECK(napi_throw_error(env, - "ERR_NAPI_TSFN_START_IDLE_LOOP", - "Failed to start the idle loop") == napi_ok); - } + if (uv_idle_start(&idle, IdleCb) != 0) { + v8::HandleScope scope(env->isolate); + CallbackScope cb_scope(this); + CHECK(napi_throw_error(env, + "ERR_NAPI_TSFN_START_IDLE_LOOP", + "Failed to start the idle loop") == napi_ok); } } @@ -3923,7 +3918,6 @@ class TsFn: public node::AsyncResource { napi_env env; void* finalize_data; napi_finalize finalize_cb; - bool idle_running; napi_threadsafe_function_call_js call_js_cb; bool handles_closing; }; From 902b07959f491e80d2f9cfc6f3ce92916f8e0cd4 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Fri, 10 Aug 2018 23:22:34 -0400 Subject: [PATCH 09/34] n-api: clean up thread-safe function * Move class `TsFn` to name space `v8impl` and rename it to `ThreadSafeFunction` * Remove `NAPI_EXTERN` from API declarations, because it's only needed in the header file. Backport-PR-URL: https://github.com/nodejs/node/pull/25002 PR-URL: https://github.com/nodejs/node/pull/22259 Reviewed-By: Anna Henningsen Reviewed-By: Kyle Farnung Reviewed-By: Michael Dawson --- src/node_api.cc | 1179 ++++++++++++++++++++++++----------------------- 1 file changed, 592 insertions(+), 587 deletions(-) diff --git a/src/node_api.cc b/src/node_api.cc index 89a51162d0ff88..a4408b70f24c10 100644 --- a/src/node_api.cc +++ b/src/node_api.cc @@ -856,180 +856,513 @@ napi_status ConcludeDeferred(napi_env env, return GET_RETURN_STATUS(env); } -} // end of namespace v8impl - -// Intercepts the Node-V8 module registration callback. Converts parameters -// to NAPI equivalents and then calls the registration callback specified -// by the NAPI module. -void napi_module_register_cb(v8::Local exports, - v8::Local module, - v8::Local context, - void* priv) { - napi_module* mod = static_cast(priv); +class ThreadSafeFunction : public node::AsyncResource { + public: + ThreadSafeFunction(v8::Local func, + v8::Local resource, + v8::Local name, + size_t thread_count_, + void* context_, + size_t max_queue_size_, + napi_env env_, + void* finalize_data_, + napi_finalize finalize_cb_, + napi_threadsafe_function_call_js call_js_cb_): + AsyncResource(env_->isolate, + resource, + *v8::String::Utf8Value(env_->isolate, name)), + thread_count(thread_count_), + is_closing(false), + context(context_), + max_queue_size(max_queue_size_), + env(env_), + finalize_data(finalize_data_), + finalize_cb(finalize_cb_), + call_js_cb(call_js_cb_ == nullptr ? CallJs : call_js_cb_), + handles_closing(false) { + ref.Reset(env->isolate, func); + node::AddEnvironmentCleanupHook(env->isolate, Cleanup, this); + } - if (mod->nm_register_func == nullptr) { - node::Environment::GetCurrent(context)->ThrowError( - "Module has no declared entry point."); - return; + ~ThreadSafeFunction() { + node::RemoveEnvironmentCleanupHook(env->isolate, Cleanup, this); + if (ref.IsEmpty()) + return; + ref.ClearWeak(); + ref.Reset(); } - // Create a new napi_env for this module or reference one if a pre-existing - // one is found. - napi_env env = v8impl::GetEnv(context); + // These methods can be called from any thread. - napi_value _exports; - NAPI_CALL_INTO_MODULE_THROW(env, - _exports = mod->nm_register_func(env, - v8impl::JsValueFromV8LocalValue(exports))); + napi_status Push(void* data, napi_threadsafe_function_call_mode mode) { + node::Mutex::ScopedLock lock(this->mutex); - // If register function returned a non-null exports object different from - // the exports object we passed it, set that as the "exports" property of - // the module. - if (_exports != nullptr && - _exports != v8impl::JsValueFromV8LocalValue(exports)) { - napi_value _module = v8impl::JsValueFromV8LocalValue(module); - napi_set_named_property(env, _module, "exports", _exports); + while (queue.size() >= max_queue_size && + max_queue_size > 0 && + !is_closing) { + if (mode == napi_tsfn_nonblocking) { + return napi_queue_full; + } + cond->Wait(lock); + } + + if (is_closing) { + if (thread_count == 0) { + return napi_invalid_arg; + } else { + thread_count--; + return napi_closing; + } + } else { + if (uv_async_send(&async) != 0) { + return napi_generic_failure; + } + queue.push(data); + return napi_ok; + } } -} -} // end of anonymous namespace + napi_status Acquire() { + node::Mutex::ScopedLock lock(this->mutex); -// Registers a NAPI module. -void napi_module_register(napi_module* mod) { - node::node_module* nm = new node::node_module { - -1, - mod->nm_flags, - nullptr, - mod->nm_filename, - nullptr, - napi_module_register_cb, - mod->nm_modname, - mod, // priv - nullptr, - }; - node::node_module_register(nm); -} + if (is_closing) { + return napi_closing; + } -napi_status napi_add_env_cleanup_hook(napi_env env, - void (*fun)(void* arg), - void* arg) { - CHECK_ENV(env); - CHECK_ARG(env, fun); + thread_count++; - node::AddEnvironmentCleanupHook(env->isolate, fun, arg); + return napi_ok; + } - return napi_ok; -} + napi_status Release(napi_threadsafe_function_release_mode mode) { + node::Mutex::ScopedLock lock(this->mutex); -napi_status napi_remove_env_cleanup_hook(napi_env env, - void (*fun)(void* arg), - void* arg) { - CHECK_ENV(env); - CHECK_ARG(env, fun); + if (thread_count == 0) { + return napi_invalid_arg; + } - node::RemoveEnvironmentCleanupHook(env->isolate, fun, arg); + thread_count--; - return napi_ok; -} + if (thread_count == 0 || mode == napi_tsfn_abort) { + if (!is_closing) { + is_closing = (mode == napi_tsfn_abort); + if (is_closing && max_queue_size > 0) { + cond->Signal(lock); + } + if (uv_async_send(&async) != 0) { + return napi_generic_failure; + } + } + } -// Warning: Keep in-sync with napi_status enum -static -const char* error_messages[] = {nullptr, - "Invalid argument", - "An object was expected", - "A string was expected", - "A string or symbol was expected", - "A function was expected", - "A number was expected", - "A boolean was expected", - "An array was expected", - "Unknown failure", - "An exception is pending", - "The async work item was cancelled", - "napi_escape_handle already called on scope", - "Invalid handle scope usage", - "Invalid callback scope usage", - "Thread-safe function queue is full", - "Thread-safe function handle is closing" -}; + return napi_ok; + } -static inline napi_status napi_clear_last_error(napi_env env) { - env->last_error.error_code = napi_ok; + void EmptyQueueAndDelete() { + for (; !queue.empty() ; queue.pop()) { + call_js_cb(nullptr, nullptr, context, queue.front()); + } + delete this; + } - // TODO(boingoing): Should this be a callback? - env->last_error.engine_error_code = 0; - env->last_error.engine_reserved = nullptr; - return napi_ok; -} + // These methods must only be called from the loop thread. -static inline -napi_status napi_set_last_error(napi_env env, napi_status error_code, - uint32_t engine_error_code, - void* engine_reserved) { - env->last_error.error_code = error_code; - env->last_error.engine_error_code = engine_error_code; - env->last_error.engine_reserved = engine_reserved; - return error_code; -} + napi_status Init() { + ThreadSafeFunction* ts_fn = this; -napi_status napi_get_last_error_info(napi_env env, - const napi_extended_error_info** result) { - CHECK_ENV(env); - CHECK_ARG(env, result); + if (uv_async_init(env->loop, &async, AsyncCb) == 0) { + if (max_queue_size > 0) { + cond.reset(new node::ConditionVariable); + } + if ((max_queue_size == 0 || cond.get() != nullptr) && + uv_idle_init(env->loop, &idle) == 0) { + return napi_ok; + } - // you must update this assert to reference the last message - // in the napi_status enum each time a new error message is added. - // We don't have a napi_status_last as this would result in an ABI - // change each time a message was added. - static_assert( - node::arraysize(error_messages) == napi_closing + 1, - "Count of error messages must match count of error values"); - CHECK_LE(env->last_error.error_code, napi_callback_scope_mismatch); + uv_close(reinterpret_cast(&async), + [] (uv_handle_t* handle) -> void { + ThreadSafeFunction* ts_fn = + node::ContainerOf(&ThreadSafeFunction::async, + reinterpret_cast(handle)); + delete ts_fn; + }); - // Wait until someone requests the last error information to fetch the error - // message string - env->last_error.error_message = - error_messages[env->last_error.error_code]; + // Prevent the thread-safe function from being deleted here, because + // the callback above will delete it. + ts_fn = nullptr; + } - *result = &(env->last_error); - return napi_ok; -} + delete ts_fn; -napi_status napi_fatal_exception(napi_env env, napi_value err) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, err); + return napi_generic_failure; + } - v8::Local local_err = v8impl::V8LocalValueFromJsValue(err); - v8impl::trigger_fatal_exception(env, local_err); + napi_status Unref() { + uv_unref(reinterpret_cast(&async)); + uv_unref(reinterpret_cast(&idle)); - return napi_clear_last_error(env); -} + return napi_ok; + } -NAPI_NO_RETURN void napi_fatal_error(const char* location, - size_t location_len, - const char* message, - size_t message_len) { - std::string location_string; - std::string message_string; + napi_status Ref() { + uv_ref(reinterpret_cast(&async)); + uv_ref(reinterpret_cast(&idle)); - if (location_len != NAPI_AUTO_LENGTH) { - location_string.assign( - const_cast(location), location_len); - } else { - location_string.assign( - const_cast(location), strlen(location)); + return napi_ok; } - if (message_len != NAPI_AUTO_LENGTH) { - message_string.assign( - const_cast(message), message_len); - } else { - message_string.assign( - const_cast(message), strlen(message)); - } + void DispatchOne() { + void* data = nullptr; + bool popped_value = false; + bool idle_stop_failed = false; - node::FatalError(location_string.c_str(), message_string.c_str()); -} + { + node::Mutex::ScopedLock lock(this->mutex); + if (is_closing) { + CloseHandlesAndMaybeDelete(); + } else { + size_t size = queue.size(); + if (size > 0) { + data = queue.front(); + queue.pop(); + popped_value = true; + if (size == max_queue_size && max_queue_size > 0) { + cond->Signal(lock); + } + size--; + } + + if (size == 0) { + if (thread_count == 0) { + is_closing = true; + if (max_queue_size > 0) { + cond->Signal(lock); + } + CloseHandlesAndMaybeDelete(); + } else { + if (uv_idle_stop(&idle) != 0) { + idle_stop_failed = true; + } + } + } + } + } + + if (popped_value || idle_stop_failed) { + v8::HandleScope scope(env->isolate); + CallbackScope cb_scope(this); + + if (idle_stop_failed) { + CHECK(napi_throw_error(env, + "ERR_NAPI_TSFN_STOP_IDLE_LOOP", + "Failed to stop the idle loop") == napi_ok); + } else { + v8::Local js_cb = + v8::Local::New(env->isolate, ref); + call_js_cb(env, + v8impl::JsValueFromV8LocalValue(js_cb), + context, + data); + } + } + } + + node::Environment* NodeEnv() { + // For some reason grabbing the Node.js environment requires a handle scope. + v8::HandleScope scope(env->isolate); + return node::Environment::GetCurrent(env->isolate); + } + + void MaybeStartIdle() { + if (uv_idle_start(&idle, IdleCb) != 0) { + v8::HandleScope scope(env->isolate); + CallbackScope cb_scope(this); + CHECK(napi_throw_error(env, + "ERR_NAPI_TSFN_START_IDLE_LOOP", + "Failed to start the idle loop") == napi_ok); + } + } + + void Finalize() { + v8::HandleScope scope(env->isolate); + if (finalize_cb) { + CallbackScope cb_scope(this); + finalize_cb(env, finalize_data, context); + } + EmptyQueueAndDelete(); + } + + inline void* Context() { + return context; + } + + void CloseHandlesAndMaybeDelete(bool set_closing = false) { + if (set_closing) { + node::Mutex::ScopedLock lock(this->mutex); + is_closing = true; + if (max_queue_size > 0) { + cond->Signal(lock); + } + } + if (handles_closing) { + return; + } + handles_closing = true; + uv_close( + reinterpret_cast(&async), + [] (uv_handle_t* handle) -> void { + ThreadSafeFunction* ts_fn = + node::ContainerOf(&ThreadSafeFunction::async, + reinterpret_cast(handle)); + uv_close( + reinterpret_cast(&ts_fn->idle), + [] (uv_handle_t* handle) -> void { + ThreadSafeFunction* ts_fn = + node::ContainerOf(&ThreadSafeFunction::idle, + reinterpret_cast(handle)); + ts_fn->Finalize(); + }); + }); + } + + // Default way of calling into JavaScript. Used when ThreadSafeFunction is + // constructed without a call_js_cb_. + static void CallJs(napi_env env, napi_value cb, void* context, void* data) { + if (!(env == nullptr || cb == nullptr)) { + napi_value recv; + napi_status status; + + status = napi_get_undefined(env, &recv); + if (status != napi_ok) { + napi_throw_error(env, "ERR_NAPI_TSFN_GET_UNDEFINED", + "Failed to retrieve undefined value"); + return; + } + + status = napi_call_function(env, recv, cb, 0, nullptr, nullptr); + if (status != napi_ok && status != napi_pending_exception) { + napi_throw_error(env, "ERR_NAPI_TSFN_CALL_JS", + "Failed to call JS callback"); + return; + } + } + } + + static void IdleCb(uv_idle_t* idle) { + ThreadSafeFunction* ts_fn = + node::ContainerOf(&ThreadSafeFunction::idle, idle); + ts_fn->DispatchOne(); + } + + static void AsyncCb(uv_async_t* async) { + ThreadSafeFunction* ts_fn = + node::ContainerOf(&ThreadSafeFunction::async, async); + ts_fn->MaybeStartIdle(); + } + + static void Cleanup(void* data) { + reinterpret_cast(data) + ->CloseHandlesAndMaybeDelete(true); + } + + private: + // These are variables protected by the mutex. + node::Mutex mutex; + std::unique_ptr cond; + std::queue queue; + uv_async_t async; + uv_idle_t idle; + size_t thread_count; + bool is_closing; + + // These are variables set once, upon creation, and then never again, which + // means we don't need the mutex to read them. + void* context; + size_t max_queue_size; + + // These are variables accessed only from the loop thread. + v8::Persistent ref; + napi_env env; + void* finalize_data; + napi_finalize finalize_cb; + napi_threadsafe_function_call_js call_js_cb; + bool handles_closing; +}; + +} // end of namespace v8impl + +// Intercepts the Node-V8 module registration callback. Converts parameters +// to NAPI equivalents and then calls the registration callback specified +// by the NAPI module. +void napi_module_register_cb(v8::Local exports, + v8::Local module, + v8::Local context, + void* priv) { + napi_module* mod = static_cast(priv); + + if (mod->nm_register_func == nullptr) { + node::Environment::GetCurrent(context)->ThrowError( + "Module has no declared entry point."); + return; + } + + // 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_value _exports; + NAPI_CALL_INTO_MODULE_THROW(env, + _exports = mod->nm_register_func(env, + v8impl::JsValueFromV8LocalValue(exports))); + + // If register function returned a non-null exports object different from + // the exports object we passed it, set that as the "exports" property of + // the module. + if (_exports != nullptr && + _exports != v8impl::JsValueFromV8LocalValue(exports)) { + napi_value _module = v8impl::JsValueFromV8LocalValue(module); + napi_set_named_property(env, _module, "exports", _exports); + } +} + +} // end of anonymous namespace + +// Registers a NAPI module. +void napi_module_register(napi_module* mod) { + node::node_module* nm = new node::node_module { + -1, + mod->nm_flags, + nullptr, + mod->nm_filename, + nullptr, + napi_module_register_cb, + mod->nm_modname, + mod, // priv + nullptr, + }; + node::node_module_register(nm); +} + +napi_status napi_add_env_cleanup_hook(napi_env env, + void (*fun)(void* arg), + void* arg) { + CHECK_ENV(env); + CHECK_ARG(env, fun); + + node::AddEnvironmentCleanupHook(env->isolate, fun, arg); + + return napi_ok; +} + +napi_status napi_remove_env_cleanup_hook(napi_env env, + void (*fun)(void* arg), + void* arg) { + CHECK_ENV(env); + CHECK_ARG(env, fun); + + node::RemoveEnvironmentCleanupHook(env->isolate, fun, arg); + + return napi_ok; +} + +// Warning: Keep in-sync with napi_status enum +static +const char* error_messages[] = {nullptr, + "Invalid argument", + "An object was expected", + "A string was expected", + "A string or symbol was expected", + "A function was expected", + "A number was expected", + "A boolean was expected", + "An array was expected", + "Unknown failure", + "An exception is pending", + "The async work item was cancelled", + "napi_escape_handle already called on scope", + "Invalid handle scope usage", + "Invalid callback scope usage", + "Thread-safe function queue is full", + "Thread-safe function handle is closing" +}; + +static inline napi_status napi_clear_last_error(napi_env env) { + env->last_error.error_code = napi_ok; + + // TODO(boingoing): Should this be a callback? + env->last_error.engine_error_code = 0; + env->last_error.engine_reserved = nullptr; + return napi_ok; +} + +static inline +napi_status napi_set_last_error(napi_env env, napi_status error_code, + uint32_t engine_error_code, + void* engine_reserved) { + env->last_error.error_code = error_code; + env->last_error.engine_error_code = engine_error_code; + env->last_error.engine_reserved = engine_reserved; + return error_code; +} + +napi_status napi_get_last_error_info(napi_env env, + const napi_extended_error_info** result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + // you must update this assert to reference the last message + // in the napi_status enum each time a new error message is added. + // We don't have a napi_status_last as this would result in an ABI + // change each time a message was added. + static_assert( + node::arraysize(error_messages) == napi_closing + 1, + "Count of error messages must match count of error values"); + CHECK_LE(env->last_error.error_code, napi_callback_scope_mismatch); + + // Wait until someone requests the last error information to fetch the error + // message string + env->last_error.error_message = + error_messages[env->last_error.error_code]; + + *result = &(env->last_error); + return napi_ok; +} + +napi_status napi_fatal_exception(napi_env env, napi_value err) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, err); + + v8::Local local_err = v8impl::V8LocalValueFromJsValue(err); + v8impl::trigger_fatal_exception(env, local_err); + + return napi_clear_last_error(env); +} + +NAPI_NO_RETURN void napi_fatal_error(const char* location, + size_t location_len, + const char* message, + size_t message_len) { + std::string location_string; + std::string message_string; + + if (location_len != NAPI_AUTO_LENGTH) { + location_string.assign( + const_cast(location), location_len); + } else { + location_string.assign( + const_cast(location), strlen(location)); + } + + if (message_len != NAPI_AUTO_LENGTH) { + message_string.assign( + const_cast(message), message_len); + } else { + message_string.assign( + const_cast(message), strlen(message)); + } + + node::FatalError(location_string.c_str(), message_string.c_str()); +} napi_status napi_create_function(napi_env env, const char* utf8name, @@ -3473,456 +3806,126 @@ napi_status napi_create_async_work(napi_env env, *result = reinterpret_cast(work); return napi_clear_last_error(env); -} - -napi_status napi_delete_async_work(napi_env env, napi_async_work work) { - CHECK_ENV(env); - CHECK_ARG(env, work); - - uvimpl::Work::Delete(reinterpret_cast(work)); - - return napi_clear_last_error(env); -} - -napi_status napi_get_uv_event_loop(napi_env env, uv_loop_t** loop) { - CHECK_ENV(env); - CHECK_ARG(env, loop); - *loop = env->loop; - return napi_clear_last_error(env); -} - -napi_status napi_queue_async_work(napi_env env, napi_async_work work) { - CHECK_ENV(env); - CHECK_ARG(env, work); - - napi_status status; - uv_loop_t* event_loop = nullptr; - status = napi_get_uv_event_loop(env, &event_loop); - if (status != napi_ok) - return napi_set_last_error(env, status); - - uvimpl::Work* w = reinterpret_cast(work); - - CALL_UV(env, uv_queue_work(event_loop, - w->Request(), - uvimpl::Work::ExecuteCallback, - uvimpl::Work::CompleteCallback)); - - return napi_clear_last_error(env); -} - -napi_status napi_cancel_async_work(napi_env env, napi_async_work work) { - CHECK_ENV(env); - CHECK_ARG(env, work); - - uvimpl::Work* w = reinterpret_cast(work); - - CALL_UV(env, uv_cancel(reinterpret_cast(w->Request()))); - - return napi_clear_last_error(env); -} - -napi_status napi_create_promise(napi_env env, - napi_deferred* deferred, - napi_value* promise) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, deferred); - CHECK_ARG(env, promise); - - auto maybe = v8::Promise::Resolver::New(env->isolate->GetCurrentContext()); - CHECK_MAYBE_EMPTY(env, maybe, napi_generic_failure); - - auto v8_resolver = maybe.ToLocalChecked(); - auto v8_deferred = new v8::Persistent(); - v8_deferred->Reset(env->isolate, v8_resolver); - - *deferred = v8impl::JsDeferredFromV8Persistent(v8_deferred); - *promise = v8impl::JsValueFromV8LocalValue(v8_resolver->GetPromise()); - return GET_RETURN_STATUS(env); -} - -napi_status napi_resolve_deferred(napi_env env, - napi_deferred deferred, - napi_value resolution) { - return v8impl::ConcludeDeferred(env, deferred, resolution, true); -} - -napi_status napi_reject_deferred(napi_env env, - napi_deferred deferred, - napi_value resolution) { - return v8impl::ConcludeDeferred(env, deferred, resolution, false); -} - -napi_status napi_is_promise(napi_env env, - napi_value promise, - bool* is_promise) { - CHECK_ENV(env); - CHECK_ARG(env, promise); - CHECK_ARG(env, is_promise); - - *is_promise = v8impl::V8LocalValueFromJsValue(promise)->IsPromise(); - - return napi_clear_last_error(env); -} - -napi_status napi_run_script(napi_env env, - napi_value script, - napi_value* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, script); - CHECK_ARG(env, result); - - v8::Local v8_script = v8impl::V8LocalValueFromJsValue(script); - - if (!v8_script->IsString()) { - return napi_set_last_error(env, napi_string_expected); - } - - v8::Local context = env->isolate->GetCurrentContext(); - - auto maybe_script = v8::Script::Compile(context, - v8::Local::Cast(v8_script)); - CHECK_MAYBE_EMPTY(env, maybe_script, napi_generic_failure); - - auto script_result = - maybe_script.ToLocalChecked()->Run(context); - CHECK_MAYBE_EMPTY(env, script_result, napi_generic_failure); - - *result = v8impl::JsValueFromV8LocalValue(script_result.ToLocalChecked()); - return GET_RETURN_STATUS(env); -} - -class TsFn: public node::AsyncResource { - public: - TsFn(v8::Local func, - v8::Local resource, - v8::Local name, - size_t thread_count_, - void* context_, - size_t max_queue_size_, - napi_env env_, - void* finalize_data_, - napi_finalize finalize_cb_, - napi_threadsafe_function_call_js call_js_cb_): - AsyncResource(env_->isolate, - resource, - *v8::String::Utf8Value(env_->isolate, name)), - thread_count(thread_count_), - is_closing(false), - context(context_), - max_queue_size(max_queue_size_), - env(env_), - finalize_data(finalize_data_), - finalize_cb(finalize_cb_), - call_js_cb(call_js_cb_ == nullptr ? CallJs : call_js_cb_), - handles_closing(false) { - ref.Reset(env->isolate, func); - node::AddEnvironmentCleanupHook(env->isolate, Cleanup, this); - } - - ~TsFn() { - node::RemoveEnvironmentCleanupHook(env->isolate, Cleanup, this); - if (ref.IsEmpty()) - return; - ref.ClearWeak(); - ref.Reset(); - } - - // These methods can be called from any thread. - - napi_status Push(void* data, napi_threadsafe_function_call_mode mode) { - node::Mutex::ScopedLock lock(this->mutex); - - while (queue.size() >= max_queue_size && - max_queue_size > 0 && - !is_closing) { - if (mode == napi_tsfn_nonblocking) { - return napi_queue_full; - } - cond->Wait(lock); - } - - if (is_closing) { - if (thread_count == 0) { - return napi_invalid_arg; - } else { - thread_count--; - return napi_closing; - } - } else { - if (uv_async_send(&async) != 0) { - return napi_generic_failure; - } - queue.push(data); - return napi_ok; - } - } - - napi_status Acquire() { - node::Mutex::ScopedLock lock(this->mutex); - - if (is_closing) { - return napi_closing; - } - - thread_count++; - - return napi_ok; - } - - napi_status Release(napi_threadsafe_function_release_mode mode) { - node::Mutex::ScopedLock lock(this->mutex); - - if (thread_count == 0) { - return napi_invalid_arg; - } - - thread_count--; - - if (thread_count == 0 || mode == napi_tsfn_abort) { - if (!is_closing) { - is_closing = (mode == napi_tsfn_abort); - if (is_closing && max_queue_size > 0) { - cond->Signal(lock); - } - if (uv_async_send(&async) != 0) { - return napi_generic_failure; - } - } - } - - return napi_ok; - } - - void EmptyQueueAndDelete() { - for (; !queue.empty() ; queue.pop()) { - call_js_cb(nullptr, nullptr, context, queue.front()); - } - delete this; - } - - // These methods must only be called from the loop thread. - - napi_status Init() { - TsFn* ts_fn = this; - - if (uv_async_init(env->loop, &async, AsyncCb) == 0) { - if (max_queue_size > 0) { - cond.reset(new node::ConditionVariable); - } - if ((max_queue_size == 0 || cond.get() != nullptr) && - uv_idle_init(env->loop, &idle) == 0) { - return napi_ok; - } +} - uv_close(reinterpret_cast(&async), - [] (uv_handle_t* handle) -> void { - TsFn* ts_fn = - node::ContainerOf(&TsFn::async, - reinterpret_cast(handle)); - delete ts_fn; - }); +napi_status napi_delete_async_work(napi_env env, napi_async_work work) { + CHECK_ENV(env); + CHECK_ARG(env, work); - // Prevent the thread-safe function from being deleted here, because - // the callback above will delete it. - ts_fn = nullptr; - } + uvimpl::Work::Delete(reinterpret_cast(work)); - delete ts_fn; + return napi_clear_last_error(env); +} - return napi_generic_failure; - } +napi_status napi_get_uv_event_loop(napi_env env, uv_loop_t** loop) { + CHECK_ENV(env); + CHECK_ARG(env, loop); + *loop = env->loop; + return napi_clear_last_error(env); +} - napi_status Unref() { - uv_unref(reinterpret_cast(&async)); - uv_unref(reinterpret_cast(&idle)); +napi_status napi_queue_async_work(napi_env env, napi_async_work work) { + CHECK_ENV(env); + CHECK_ARG(env, work); - return napi_ok; - } + napi_status status; + uv_loop_t* event_loop = nullptr; + status = napi_get_uv_event_loop(env, &event_loop); + if (status != napi_ok) + return napi_set_last_error(env, status); - napi_status Ref() { - uv_ref(reinterpret_cast(&async)); - uv_ref(reinterpret_cast(&idle)); + uvimpl::Work* w = reinterpret_cast(work); - return napi_ok; - } + CALL_UV(env, uv_queue_work(event_loop, + w->Request(), + uvimpl::Work::ExecuteCallback, + uvimpl::Work::CompleteCallback)); - void DispatchOne() { - void* data = nullptr; - bool popped_value = false; - bool idle_stop_failed = false; + return napi_clear_last_error(env); +} - { - node::Mutex::ScopedLock lock(this->mutex); - if (is_closing) { - CloseHandlesAndMaybeDelete(); - } else { - size_t size = queue.size(); - if (size > 0) { - data = queue.front(); - queue.pop(); - popped_value = true; - if (size == max_queue_size && max_queue_size > 0) { - cond->Signal(lock); - } - size--; - } +napi_status napi_cancel_async_work(napi_env env, napi_async_work work) { + CHECK_ENV(env); + CHECK_ARG(env, work); - if (size == 0) { - if (thread_count == 0) { - is_closing = true; - if (max_queue_size > 0) { - cond->Signal(lock); - } - CloseHandlesAndMaybeDelete(); - } else { - if (uv_idle_stop(&idle) != 0) { - idle_stop_failed = true; - } - } - } - } - } + uvimpl::Work* w = reinterpret_cast(work); - if (popped_value || idle_stop_failed) { - v8::HandleScope scope(env->isolate); - CallbackScope cb_scope(this); + CALL_UV(env, uv_cancel(reinterpret_cast(w->Request()))); - if (idle_stop_failed) { - CHECK(napi_throw_error(env, - "ERR_NAPI_TSFN_STOP_IDLE_LOOP", - "Failed to stop the idle loop") == napi_ok); - } else { - v8::Local js_cb = - v8::Local::New(env->isolate, ref); - call_js_cb(env, - v8impl::JsValueFromV8LocalValue(js_cb), - context, - data); - } - } - } + return napi_clear_last_error(env); +} - node::Environment* NodeEnv() { - // For some reason grabbing the Node.js environment requires a handle scope. - v8::HandleScope scope(env->isolate); - return node::Environment::GetCurrent(env->isolate); - } +napi_status napi_create_promise(napi_env env, + napi_deferred* deferred, + napi_value* promise) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, deferred); + CHECK_ARG(env, promise); - void MaybeStartIdle() { - if (uv_idle_start(&idle, IdleCb) != 0) { - v8::HandleScope scope(env->isolate); - CallbackScope cb_scope(this); - CHECK(napi_throw_error(env, - "ERR_NAPI_TSFN_START_IDLE_LOOP", - "Failed to start the idle loop") == napi_ok); - } - } + auto maybe = v8::Promise::Resolver::New(env->isolate->GetCurrentContext()); + CHECK_MAYBE_EMPTY(env, maybe, napi_generic_failure); - void Finalize() { - v8::HandleScope scope(env->isolate); - if (finalize_cb) { - CallbackScope cb_scope(this); - finalize_cb(env, finalize_data, context); - } - EmptyQueueAndDelete(); - } + auto v8_resolver = maybe.ToLocalChecked(); + auto v8_deferred = new v8::Persistent(); + v8_deferred->Reset(env->isolate, v8_resolver); - inline void* Context() { - return context; - } + *deferred = v8impl::JsDeferredFromV8Persistent(v8_deferred); + *promise = v8impl::JsValueFromV8LocalValue(v8_resolver->GetPromise()); + return GET_RETURN_STATUS(env); +} - void CloseHandlesAndMaybeDelete(bool set_closing = false) { - if (set_closing) { - node::Mutex::ScopedLock lock(this->mutex); - is_closing = true; - if (max_queue_size > 0) { - cond->Signal(lock); - } - } - if (handles_closing) { - return; - } - handles_closing = true; - uv_close( - reinterpret_cast(&async), - [] (uv_handle_t* handle) -> void { - TsFn* ts_fn = node::ContainerOf(&TsFn::async, - reinterpret_cast(handle)); - uv_close( - reinterpret_cast(&ts_fn->idle), - [] (uv_handle_t* handle) -> void { - TsFn* ts_fn = node::ContainerOf(&TsFn::idle, - reinterpret_cast(handle)); - ts_fn->Finalize(); - }); - }); - } +napi_status napi_resolve_deferred(napi_env env, + napi_deferred deferred, + napi_value resolution) { + return v8impl::ConcludeDeferred(env, deferred, resolution, true); +} - // Default way of calling into JavaScript. Used when TsFn is constructed - // without a call_js_cb_. - static void CallJs(napi_env env, napi_value cb, void* context, void* data) { - if (!(env == nullptr || cb == nullptr)) { - napi_value recv; - napi_status status; +napi_status napi_reject_deferred(napi_env env, + napi_deferred deferred, + napi_value resolution) { + return v8impl::ConcludeDeferred(env, deferred, resolution, false); +} - status = napi_get_undefined(env, &recv); - if (status != napi_ok) { - napi_throw_error(env, "ERR_NAPI_TSFN_GET_UNDEFINED", - "Failed to retrieve undefined value"); - return; - } +napi_status napi_is_promise(napi_env env, + napi_value promise, + bool* is_promise) { + CHECK_ENV(env); + CHECK_ARG(env, promise); + CHECK_ARG(env, is_promise); - status = napi_call_function(env, recv, cb, 0, nullptr, nullptr); - if (status != napi_ok && status != napi_pending_exception) { - napi_throw_error(env, "ERR_NAPI_TSFN_CALL_JS", - "Failed to call JS callback"); - return; - } - } - } + *is_promise = v8impl::V8LocalValueFromJsValue(promise)->IsPromise(); - static void IdleCb(uv_idle_t* idle) { - TsFn* ts_fn = - node::ContainerOf(&TsFn::idle, idle); - ts_fn->DispatchOne(); - } + return napi_clear_last_error(env); +} - static void AsyncCb(uv_async_t* async) { - TsFn* ts_fn = - node::ContainerOf(&TsFn::async, async); - ts_fn->MaybeStartIdle(); - } +napi_status napi_run_script(napi_env env, + napi_value script, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, script); + CHECK_ARG(env, result); - static void Cleanup(void* data) { - reinterpret_cast(data)->CloseHandlesAndMaybeDelete(true); + v8::Local v8_script = v8impl::V8LocalValueFromJsValue(script); + + if (!v8_script->IsString()) { + return napi_set_last_error(env, napi_string_expected); } - private: - // These are variables protected by the mutex. - node::Mutex mutex; - std::unique_ptr cond; - std::queue queue; - uv_async_t async; - uv_idle_t idle; - size_t thread_count; - bool is_closing; + v8::Local context = env->isolate->GetCurrentContext(); - // These are variables set once, upon creation, and then never again, which - // means we don't need the mutex to read them. - void* context; - size_t max_queue_size; + auto maybe_script = v8::Script::Compile(context, + v8::Local::Cast(v8_script)); + CHECK_MAYBE_EMPTY(env, maybe_script, napi_generic_failure); - // These are variables accessed only from the loop thread. - v8::Persistent ref; - napi_env env; - void* finalize_data; - napi_finalize finalize_cb; - napi_threadsafe_function_call_js call_js_cb; - bool handles_closing; -}; + auto script_result = + maybe_script.ToLocalChecked()->Run(context); + CHECK_MAYBE_EMPTY(env, script_result, napi_generic_failure); + + *result = v8impl::JsValueFromV8LocalValue(script_result.ToLocalChecked()); + return GET_RETURN_STATUS(env); +} -NAPI_EXTERN napi_status +napi_status napi_create_threadsafe_function(napi_env env, napi_value func, napi_value async_resource, @@ -3957,16 +3960,17 @@ napi_create_threadsafe_function(napi_env env, v8::Local v8_name; CHECK_TO_STRING(env, v8_context, v8_name, async_resource_name); - TsFn* ts_fn = new TsFn(v8_func, - v8_resource, - v8_name, - initial_thread_count, - context, - max_queue_size, - env, - thread_finalize_data, - thread_finalize_cb, - call_js_cb); + v8impl::ThreadSafeFunction* ts_fn = + new v8impl::ThreadSafeFunction(v8_func, + v8_resource, + v8_name, + initial_thread_count, + context, + max_queue_size, + env, + thread_finalize_data, + thread_finalize_cb, + call_js_cb); if (ts_fn == nullptr) { status = napi_generic_failure; @@ -3981,45 +3985,46 @@ napi_create_threadsafe_function(napi_env env, return napi_set_last_error(env, status); } -NAPI_EXTERN napi_status +napi_status napi_get_threadsafe_function_context(napi_threadsafe_function func, void** result) { CHECK(func != nullptr); CHECK(result != nullptr); - *result = reinterpret_cast(func)->Context(); + *result = reinterpret_cast(func)->Context(); return napi_ok; } -NAPI_EXTERN napi_status +napi_status napi_call_threadsafe_function(napi_threadsafe_function func, void* data, napi_threadsafe_function_call_mode is_blocking) { CHECK(func != nullptr); - return reinterpret_cast(func)->Push(data, is_blocking); + return reinterpret_cast(func)->Push(data, + is_blocking); } -NAPI_EXTERN napi_status +napi_status napi_acquire_threadsafe_function(napi_threadsafe_function func) { CHECK(func != nullptr); - return reinterpret_cast(func)->Acquire(); + return reinterpret_cast(func)->Acquire(); } -NAPI_EXTERN napi_status +napi_status napi_release_threadsafe_function(napi_threadsafe_function func, napi_threadsafe_function_release_mode mode) { CHECK(func != nullptr); - return reinterpret_cast(func)->Release(mode); + return reinterpret_cast(func)->Release(mode); } -NAPI_EXTERN napi_status +napi_status napi_unref_threadsafe_function(napi_env env, napi_threadsafe_function func) { CHECK(func != nullptr); - return reinterpret_cast(func)->Unref(); + return reinterpret_cast(func)->Unref(); } -NAPI_EXTERN napi_status +napi_status napi_ref_threadsafe_function(napi_env env, napi_threadsafe_function func) { CHECK(func != nullptr); - return reinterpret_cast(func)->Ref(); + return reinterpret_cast(func)->Ref(); } From 6b7cccc88aeb91a6e07a29ab296a7d49f80b614b Mon Sep 17 00:00:00 2001 From: Lars-Magnus Skog Date: Fri, 21 Sep 2018 16:17:38 +0200 Subject: [PATCH 10/34] doc: fix optional parameters in n-api.md The thread_finalize_data and thread_finalize_cb parameters in napi_create_threadsafe_function are optional. Backport-PR-URL: https://github.com/nodejs/node/pull/25002 PR-URL: https://github.com/nodejs/node/pull/22998 Reviewed-By: James M Snell Reviewed-By: Anna Henningsen --- doc/api/n-api.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api/n-api.md b/doc/api/n-api.md index 7e7ed9efd3e88a..91368267c42663 100644 --- a/doc/api/n-api.md +++ b/doc/api/n-api.md @@ -4056,8 +4056,8 @@ by the `async_hooks` API. - `[in] max_queue_size`: Maximum size of the queue. 0 for no limit. - `[in] initial_thread_count`: The initial number of threads, including the main thread, which will be making use of this function. -- `[in] thread_finalize_data`: Data to be passed to `thread_finalize_cb`. -- `[in] thread_finalize_cb`: Function to call when the +- `[in] thread_finalize_data`: Optional data to be passed to `thread_finalize_cb`. +- `[in] thread_finalize_cb`: Optional function to call when the `napi_threadsafe_function` is being destroyed. - `[in] context`: Optional data to attach to the resulting `napi_threadsafe_function`. From fe4328252a95f8096dd559d981bcab26a39dd70a Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Wed, 31 Oct 2018 15:41:32 +0100 Subject: [PATCH 11/34] n-api: add missing handle scopes Currently when building with --debug test/addons-napi/test_threadsafe_function will error: $ out/Debug/node test/addons-napi/test_threadsafe_function/test.js FATAL ERROR: v8::HandleScope::CreateHandle() Cannot create a handle without a HandleScope 1: 0x10004e287 node::DumpBacktrace(__sFILE*) [node/out/Debug/node] 2: 0x1000cd37b node::Abort() [/node/out/Debug/node] 3: 0x1000cd69f node::OnFatalError(char const*, char const*) [/node/out/Debug/node] 4: 0x1004df0b1 v8::Utils::ReportApiFailure(char const*, char const*) [/nodejs/node/out/Debug/node] 5: 0x100a8c0a9 v8::internal::HandleScope::Extend( v8::internal::Isolate*) [/node/out/Debug/node] 6: 0x1004e4229 v8::EmbedderDataFor(v8::Context*, int, bool, char const*) [/node/out/Debug/node] 7: 0x1004e43fa v8::Context::SlowGetAlignedPointerFromEmbedderData(int) [/node/out/Debug/node] 8: 0x10001c26b v8::Context::GetAlignedPointerFromEmbedderData(int) [/node/out/Debug/node] 9: 0x1000144ea node::Environment::GetCurrent(v8::Local) [/node/out/Debug/node] 10: 0x1000f49e2 napi_env__::node_env() const [/node/out/Debug/node] 11: 0x1000f9885 (anonymous namespace)::v8impl::ThreadSafeFunction:: CloseHandlesAndMaybeDelete(bool) [/node/out/Debug/node] 12: 0x1000fb34f (anonymous namespace)::v8impl::ThreadSafeFunction:: DispatchOne() [/node/out/Debug/node] 13: 0x1000fb129 (anonymous namespace)::v8impl::ThreadSafeFunction:: IdleCb(uv_idle_s*) [/node/out/Debug/node] 14: 0x1011a1b69 uv__run_idle [/node/out/Debug/node] 15: 0x101198179 uv_run [/node/out/Debug/node] 16: 0x1000dfca1 node::Start(...) [/node/out/Debug/node] 17: 0x1000dae50 node::Start(...) [/node/out/Debug/node] 18: 0x1000da56f node::Start(int, char**) [/node/out/Debug/node] 19: 0x10141112e main [/node/out/Debug/node] 20: 0x100001034 start [/node/out/Debug/node] Abort trap: 6 This commit adds two HandleScope's, one to CloseHandlesAndMaybeDelete and one to the lambda. SlowGetAlignedPointerFromEmbedderData will only be called for debug builds: https://github.com/v8/v8/blob/2ef0aa662fe907a1b36ac1abe7d77ad2bcd27733 /include/v8.h#L10440-L10447 Backport-PR-URL: https://github.com/nodejs/node/pull/25002 PR-URL: https://github.com/nodejs/node/pull/24011 Reviewed-By: James M Snell Reviewed-By: Michael Dawson --- src/node_api.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/node_api.cc b/src/node_api.cc index a4408b70f24c10..196f5512c9cd0f 100644 --- a/src/node_api.cc +++ b/src/node_api.cc @@ -1097,6 +1097,7 @@ class ThreadSafeFunction : public node::AsyncResource { } void CloseHandlesAndMaybeDelete(bool set_closing = false) { + v8::HandleScope scope(env->isolate); if (set_closing) { node::Mutex::ScopedLock lock(this->mutex); is_closing = true; @@ -1114,6 +1115,7 @@ class ThreadSafeFunction : public node::AsyncResource { ThreadSafeFunction* ts_fn = node::ContainerOf(&ThreadSafeFunction::async, reinterpret_cast(handle)); + v8::HandleScope scope(ts_fn->env->isolate); uv_close( reinterpret_cast(&ts_fn->idle), [] (uv_handle_t* handle) -> void { From 60db455961477244c6ca4fcf17bcd2c2e90820ca Mon Sep 17 00:00:00 2001 From: Gireesh Punathil Date: Thu, 29 Nov 2018 10:26:53 +0530 Subject: [PATCH 12/34] test: mark test_threadsafe_function/test as flaky The test fails consistently on windows-fanned with vs2017. mark it as flaky while the issue is being progressed, and to keep CI green / amber. Ref: https://github.com/nodejs/node/issues/23621 Backport-PR-URL: https://github.com/nodejs/node/pull/25002 PR-URL: https://github.com/nodejs/node/pull/24714 Reviewed-By: James M Snell Reviewed-By: Rich Trott --- test/addons-napi/addons-napi.status | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 test/addons-napi/addons-napi.status diff --git a/test/addons-napi/addons-napi.status b/test/addons-napi/addons-napi.status new file mode 100644 index 00000000000000..dffcf3787bfe47 --- /dev/null +++ b/test/addons-napi/addons-napi.status @@ -0,0 +1,11 @@ +prefix addons-napi + +# To mark a test as flaky, list the test name in the appropriate section +# below, without ".js", followed by ": PASS,FLAKY". Example: +# sample-test : PASS,FLAKY + +[true] # This section applies to all platforms + +[$system==win32] +# https://github.com/nodejs/node/issues/23621 +test_threadsafe_function/test: PASS,FLAKY From d4b6643ac333863efd417732ac641582e1a6211d Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Thu, 13 Dec 2018 22:58:39 -0800 Subject: [PATCH 13/34] test: mark test-cli-node-options flaky on arm Refs: https://github.com/nodejs/node/issues/25028 PR-URL: https://github.com/nodejs/node/pull/25032 Reviewed-By: Gireesh Punathil Reviewed-By: Colin Ihrig Reviewed-By: Daijiro Wachi --- test/parallel/parallel.status | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/parallel/parallel.status b/test/parallel/parallel.status index 8163bc672f34a5..bfb0f8289c850b 100644 --- a/test/parallel/parallel.status +++ b/test/parallel/parallel.status @@ -16,6 +16,8 @@ test-postmortem-metadata: PASS,FLAKY [$arch==arm || $arch==arm64] test-npm-install: PASS,FLAKY +# https://github.com/nodejs/node/issues/25028 +test-cli-node-options: PASS,FLAKY [$system==solaris] # Also applies to SmartOS From f6ff8c51bc6b97882b6d60b6b7f7db099539de00 Mon Sep 17 00:00:00 2001 From: Richard Lau Date: Wed, 9 Jan 2019 17:00:06 +0000 Subject: [PATCH 14/34] test: fix module loading error for AIX 7.1 AIX 7.1 appears to return a different error message compared to AIX 6.1. PR-URL: https://github.com/nodejs/node/pull/25418 Reviewed-By: Beth Griggs Reviewed-By: Gireesh Punathil Reviewed-By: Colin Ihrig Reviewed-By: George Adams Reviewed-By: Michael Dawson --- test/parallel/test-module-loading-error.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/parallel/test-module-loading-error.js b/test/parallel/test-module-loading-error.js index 68f45d774e5ce0..9747f58fe5cd45 100644 --- a/test/parallel/test-module-loading-error.js +++ b/test/parallel/test-module-loading-error.js @@ -30,7 +30,8 @@ const errorMessagesByPlatform = { sunos: ['unknown file type', 'not an ELF file'], darwin: ['file too short'], aix: ['Cannot load module', - 'Cannot run a file that does not have a valid format.'] + 'Cannot run a file that does not have a valid format.', + 'Exec format error'] }; // If we don't know a priori what the error would be, we accept anything. const errorMessages = errorMessagesByPlatform[process.platform] || ['']; From e9369073d949979e51cee03a61f82c5c4f604e10 Mon Sep 17 00:00:00 2001 From: Richard Lau Date: Fri, 11 Jan 2019 16:36:33 +0000 Subject: [PATCH 15/34] build: set `-blibpath:` for AIX https://github.com/nodejs/node/pull/17604 refactored the gyp files so that `-blibpath:` on AIX was only set if `node_shared=="true"`. Restore the setting for non-shared builds. Fixes: https://github.com/nodejs/node/issues/25444 Backport-PR-URL: https://github.com/nodejs/node/pull/25521 PR-URL: https://github.com/nodejs/node/pull/25447 Reviewed-By: Gireesh Punathil Reviewed-By: Michael Dawson --- common.gypi | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/common.gypi b/common.gypi index fd42f27d351282..b423c9a61a099e 100644 --- a/common.gypi +++ b/common.gypi @@ -88,6 +88,19 @@ ['OS=="aix"', { 'cflags': [ '-gxcoff' ], 'ldflags': [ '-Wl,-bbigtoc' ], + 'conditions': [ + ['target_arch=="ppc64"', { + 'ldflags': [ + '-Wl,-blibpath:/usr/lib:/lib:' + '/opt/freeware/lib/pthread/ppc64' + ], + }], + ['target_arch=="ppc"', { + 'ldflags': [ + '-Wl,-blibpath:/usr/lib:/lib:/opt/freeware/lib/pthread' + ], + }], + ], }], ['OS == "android"', { 'cflags': [ '-fPIE' ], @@ -337,11 +350,18 @@ [ 'OS=="aix"', { 'conditions': [ [ 'target_arch=="ppc"', { - 'ldflags': [ '-Wl,-bmaxdata:0x60000000/dsa' ], + 'ldflags': [ + '-Wl,-bmaxdata:0x60000000/dsa', + '-Wl,-blibpath:/usr/lib:/lib:/opt/freeware/lib/pthread', + ], }], [ 'target_arch=="ppc64"', { 'cflags': [ '-maix64' ], - 'ldflags': [ '-maix64' ], + 'ldflags': [ + '-maix64', + '-Wl,-blibpath:/usr/lib:/lib:' + '/opt/freeware/lib/pthread/ppc64', + ], }], ], 'ldflags': [ '-Wl,-bbigtoc' ], From 1a9582b7a66759d1c478f5b6b2adc91e39cc982a Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Mon, 24 Sep 2018 11:47:13 +0200 Subject: [PATCH 16/34] tools: allow input for TTY tests Since faking TTY input is not otherwise fake-able, we need support in the test runner for it. Backport-PR-URL: https://github.com/nodejs/node/pull/25351 PR-URL: https://github.com/nodejs/node/pull/23053 Reviewed-By: James M Snell --- test/pseudo-tty/testcfg.py | 12 +++++++++--- tools/test.py | 13 ++++++++++++- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/test/pseudo-tty/testcfg.py b/test/pseudo-tty/testcfg.py index 40396db247e279..c6c93c13b98340 100644 --- a/test/pseudo-tty/testcfg.py +++ b/test/pseudo-tty/testcfg.py @@ -35,10 +35,11 @@ class TTYTestCase(test.TestCase): - def __init__(self, path, file, expected, arch, mode, context, config): + def __init__(self, path, file, expected, input, arch, mode, context, config): super(TTYTestCase, self).__init__(context, path, arch, mode) self.file = file self.expected = expected + self.input = input self.config = config self.arch = arch self.mode = mode @@ -104,12 +105,16 @@ def GetSource(self): + open(self.expected).read()) def RunCommand(self, command, env): + input = None + if self.input is not None and exists(self.input): + input = open(self.input).read() full_command = self.context.processor(command) output = test.Execute(full_command, self.context, self.context.GetTimeout(self.mode), env, - True) + faketty=True, + input=input) self.Cleanup() return test.TestOutput(self, full_command, @@ -140,11 +145,12 @@ def ListTests(self, current_path, path, arch, mode): if self.Contains(path, test): file_prefix = join(self.root, reduce(join, test[1:], "")) file_path = file_prefix + ".js" + input_path = file_prefix + ".in" output_path = file_prefix + ".out" if not exists(output_path): raise Exception("Could not find %s" % output_path) result.append(TTYTestCase(test, file_path, output_path, - arch, mode, self.context, self)) + input_path, arch, mode, self.context, self)) return result def GetBuildRequirements(self): diff --git a/tools/test.py b/tools/test.py index dafcc3d2ad4122..a51b475b1f23cd 100755 --- a/tools/test.py +++ b/tools/test.py @@ -717,12 +717,23 @@ def CheckedUnlink(name): PrintError("os.unlink() " + str(e)) break -def Execute(args, context, timeout=None, env={}, faketty=False, disable_core_files=False): +def Execute(args, context, timeout=None, env={}, faketty=False, disable_core_files=False, input=None): if faketty: import pty (out_master, fd_out) = pty.openpty() fd_in = fd_err = fd_out pty_out = out_master + + if input is not None: + # Before writing input data, disable echo so the input doesn't show + # up as part of the output. + import termios + attr = termios.tcgetattr(fd_in) + attr[3] = attr[3] & ~termios.ECHO + termios.tcsetattr(fd_in, termios.TCSADRAIN, attr) + + os.write(pty_out, input) + os.write(pty_out, '\x04') # End-of-file marker (Ctrl+D) else: (fd_out, outname) = tempfile.mkstemp() (fd_err, errname) = tempfile.mkstemp() From ea5628e77ae12bdb34e4c1514261ea27c45f6562 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Mon, 24 Sep 2018 11:51:14 +0200 Subject: [PATCH 17/34] process: allow reading from stdout/stderr sockets Allow reading from stdio streams that are conventionally associated with process output, since this is only convention. This involves disabling the oddness around closing stdio streams. Its purpose is to prevent the file descriptors 0 through 2 from being closed, since doing so can lead to information leaks when new file descriptors are being opened; instead, not doing anything seems like a more reasonable choice. Fixes: https://github.com/nodejs/node/issues/21203 Backport-PR-URL: https://github.com/nodejs/node/pull/25351 PR-URL: https://github.com/nodejs/node/pull/23053 Reviewed-By: James M Snell --- doc/api/errors.md | 20 +++++++++++++++++++ doc/api/process.md | 6 +----- lib/internal/errors.js | 2 -- lib/internal/process/stdio.js | 18 +++++++---------- ...out-cannot-be-closed-child-process-pipe.js | 4 ++-- test/pseudo-tty/test-stdout-read.in | 1 + test/pseudo-tty/test-stdout-read.js | 3 +++ test/pseudo-tty/test-stdout-read.out | 1 + 8 files changed, 35 insertions(+), 20 deletions(-) create mode 100644 test/pseudo-tty/test-stdout-read.in create mode 100644 test/pseudo-tty/test-stdout-read.js create mode 100644 test/pseudo-tty/test-stdout-read.out diff --git a/doc/api/errors.md b/doc/api/errors.md index 590c504ae68edf..dbc1c0e48b35af 100755 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -1172,12 +1172,32 @@ A call was made and the UDP subsystem was not running. ### ERR_STDERR_CLOSE + + An attempt was made to close the `process.stderr` stream. By design, Node.js does not allow `stdout` or `stderr` streams to be closed by user code. ### ERR_STDOUT_CLOSE + + An attempt was made to close the `process.stdout` stream. By design, Node.js does not allow `stdout` or `stderr` streams to be closed by user code. diff --git a/doc/api/process.md b/doc/api/process.md index 8279f596a51fc1..73f6dbda7e332e 100644 --- a/doc/api/process.md +++ b/doc/api/process.md @@ -1703,9 +1703,7 @@ important ways: 1. They are used internally by [`console.log()`][] and [`console.error()`][], respectively. -2. They cannot be closed ([`end()`][] will throw). -3. They will never emit the [`'finish'`][] event. -4. Writes may be synchronous depending on what the stream is connected to +2. Writes may be synchronous depending on what the stream is connected to and whether the system is Windows or POSIX: - Files: *synchronous* on Windows and POSIX - TTYs (Terminals): *asynchronous* on Windows, *synchronous* on POSIX @@ -1925,7 +1923,6 @@ cases: [`'exit'`]: #process_event_exit -[`'finish'`]: stream.html#stream_event_finish [`'message'`]: child_process.html#child_process_event_message [`'rejectionHandled'`]: #process_event_rejectionhandled [`'uncaughtException'`]: #process_event_uncaughtexception @@ -1936,7 +1933,6 @@ cases: [`EventEmitter`]: events.html#events_class_eventemitter [`console.error()`]: console.html#console_console_error_data_args [`console.log()`]: console.html#console_console_log_data_args -[`end()`]: stream.html#stream_writable_end_chunk_encoding_callback [`net.Server`]: net.html#net_class_net_server [`net.Socket`]: net.html#net_class_net_socket [`process.argv`]: #process_process_argv diff --git a/lib/internal/errors.js b/lib/internal/errors.js index e878766f680605..c260b547b8a7e8 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -427,8 +427,6 @@ E('ERR_SOCKET_BUFFER_SIZE', (reason) => `Could not get or set buffer size: ${reason}`); E('ERR_SOCKET_CANNOT_SEND', 'Unable to send data'); E('ERR_SOCKET_DGRAM_NOT_RUNNING', 'Not running'); -E('ERR_STDERR_CLOSE', 'process.stderr cannot be closed'); -E('ERR_STDOUT_CLOSE', 'process.stdout cannot be closed'); E('ERR_UNKNOWN_BUILTIN_MODULE', (id) => `No such built-in module: ${id}`); E('ERR_UNKNOWN_FILE_EXTENSION', 'Unknown file extension: %s'); E('ERR_UNKNOWN_MODULE_FORMAT', 'Unknown module format: %s'); diff --git a/lib/internal/process/stdio.js b/lib/internal/process/stdio.js index 7faef381f895bf..46641b5871646f 100644 --- a/lib/internal/process/stdio.js +++ b/lib/internal/process/stdio.js @@ -1,9 +1,11 @@ 'use strict'; -const errors = require('internal/errors'); +const errors = require('internal/errors').codes; exports.setup = setupStdio; +function dummyDestroy(err, cb) { cb(err); } + function setupStdio() { var stdin; var stdout; @@ -13,11 +15,8 @@ function setupStdio() { if (stdout) return stdout; stdout = createWritableStdioStream(1); stdout.destroySoon = stdout.destroy; - stdout._destroy = function(er, cb) { - // Avoid errors if we already emitted - er = er || new errors.Error('ERR_STDOUT_CLOSE'); - cb(er); - }; + // Override _destroy so that the fd is never actually closed. + stdout._destroy = dummyDestroy; if (stdout.isTTY) { process.on('SIGWINCH', () => stdout._refreshSize()); } @@ -28,11 +27,8 @@ function setupStdio() { if (stderr) return stderr; stderr = createWritableStdioStream(2); stderr.destroySoon = stderr.destroy; - stderr._destroy = function(er, cb) { - // Avoid errors if we already emitted - er = er || new errors.Error('ERR_STDERR_CLOSE'); - cb(er); - }; + // Override _destroy so that the fd is never actually closed. + stdout._destroy = dummyDestroy; if (stderr.isTTY) { process.on('SIGWINCH', () => stderr._refreshSize()); } diff --git a/test/parallel/test-stdout-cannot-be-closed-child-process-pipe.js b/test/parallel/test-stdout-cannot-be-closed-child-process-pipe.js index d19b522e290ba9..7cd4b90c008a2f 100644 --- a/test/parallel/test-stdout-cannot-be-closed-child-process-pipe.js +++ b/test/parallel/test-stdout-cannot-be-closed-child-process-pipe.js @@ -24,9 +24,9 @@ function parent() { }); child.on('close', function(code, signal) { - assert(code); + assert.strictEqual(code, 0); + assert.strictEqual(err, ''); assert.strictEqual(out, 'foo'); - assert(/process\.stdout cannot be closed/.test(err)); console.log('ok'); }); } diff --git a/test/pseudo-tty/test-stdout-read.in b/test/pseudo-tty/test-stdout-read.in new file mode 100644 index 00000000000000..10ddd6d257e013 --- /dev/null +++ b/test/pseudo-tty/test-stdout-read.in @@ -0,0 +1 @@ +Hello! diff --git a/test/pseudo-tty/test-stdout-read.js b/test/pseudo-tty/test-stdout-read.js new file mode 100644 index 00000000000000..90f017ed77b7be --- /dev/null +++ b/test/pseudo-tty/test-stdout-read.js @@ -0,0 +1,3 @@ +'use strict'; +const common = require('../common'); +process.stderr.on('data', common.mustCall(console.log)); diff --git a/test/pseudo-tty/test-stdout-read.out b/test/pseudo-tty/test-stdout-read.out new file mode 100644 index 00000000000000..3b7fda223d0e6c --- /dev/null +++ b/test/pseudo-tty/test-stdout-read.out @@ -0,0 +1 @@ + From c56f3edb103b03ee9b8aaa0b90a36b61278c2aea Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Mon, 24 Sep 2018 12:08:46 +0200 Subject: [PATCH 18/34] test: add stdin writable regression test Make sure that `process.stdin.write()`, and in particular ending the stream, works. Backport-PR-URL: https://github.com/nodejs/node/pull/25351 PR-URL: https://github.com/nodejs/node/pull/23053 Reviewed-By: James M Snell --- test/pseudo-tty/test-stdin-write.js | 3 +++ test/pseudo-tty/test-stdin-write.out | 1 + 2 files changed, 4 insertions(+) create mode 100644 test/pseudo-tty/test-stdin-write.js create mode 100644 test/pseudo-tty/test-stdin-write.out diff --git a/test/pseudo-tty/test-stdin-write.js b/test/pseudo-tty/test-stdin-write.js new file mode 100644 index 00000000000000..39091f20bbb745 --- /dev/null +++ b/test/pseudo-tty/test-stdin-write.js @@ -0,0 +1,3 @@ +'use strict'; +require('../common'); +process.stdin.end('foobar\n'); diff --git a/test/pseudo-tty/test-stdin-write.out b/test/pseudo-tty/test-stdin-write.out new file mode 100644 index 00000000000000..323fae03f4606e --- /dev/null +++ b/test/pseudo-tty/test-stdin-write.out @@ -0,0 +1 @@ +foobar From b4c5435a46235534062edeb26adef599de7d1b31 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 17 Sep 2018 13:21:47 +0200 Subject: [PATCH 19/34] test: add process.stdin.end() TTY regression test Backport-PR-URL: https://github.com/nodejs/node/pull/25351 PR-URL: https://github.com/nodejs/node/pull/23051 Fixes: https://github.com/nodejs/node/issues/22814 Reviewed-By: Matteo Collina Reviewed-By: Colin Ihrig Reviewed-By: Trivikram Kamat Reviewed-By: Luigi Pinca Reviewed-By: George Adams Reviewed-By: James M Snell --- test/pseudo-tty/test-tty-stdin-call-end.js | 8 ++++++++ test/pseudo-tty/test-tty-stdin-call-end.out | 0 2 files changed, 8 insertions(+) create mode 100644 test/pseudo-tty/test-tty-stdin-call-end.js create mode 100644 test/pseudo-tty/test-tty-stdin-call-end.out diff --git a/test/pseudo-tty/test-tty-stdin-call-end.js b/test/pseudo-tty/test-tty-stdin-call-end.js new file mode 100644 index 00000000000000..e3c3fd9af469ba --- /dev/null +++ b/test/pseudo-tty/test-tty-stdin-call-end.js @@ -0,0 +1,8 @@ +'use strict'; + +require('../common'); + +// This tests verifies that process.stdin.end() does not +// crash the process with ENOTCONN + +process.stdin.end(); diff --git a/test/pseudo-tty/test-tty-stdin-call-end.out b/test/pseudo-tty/test-tty-stdin-call-end.out new file mode 100644 index 00000000000000..e69de29bb2d1d6 From 44609d1274a1dca879f87d22143137a56f729f48 Mon Sep 17 00:00:00 2001 From: Kyle Farnung Date: Thu, 7 Dec 2017 15:01:13 -0800 Subject: [PATCH 20/34] n-api: restrict exports by version * Move `napi_get_uv_event_loop` into the `NAPI_VERSION >= 2` section * Move `napi_open_callback_scope`, `napi_close_callback_scope`, `napi_fatal_exception`, `napi_add_env_cleanup_hook`, and `napi_remove_env_cleanup_hook` into the `NAPI_VERSION >= 3` section * Added a missing `added` property to `napi_get_uv_event_loop` in the docs * Added a `napiVersion` property to the docs and updated the parser and generator to use it. * Added usage documentation PR-URL: https://github.com/nodejs/node/pull/19962 Backport-PR-URL: https://github.com/nodejs/node/pull/25648 Reviewed-By: Gabriel Schulhof Reviewed-By: Michael Dawson --- doc/api/n-api.md | 158 ++++++++++++++++++++++++++++++++++++++++++++ src/node_api.h | 55 ++++++++++----- tools/doc/common.js | 4 ++ tools/doc/html.js | 4 ++ 4 files changed, 204 insertions(+), 17 deletions(-) diff --git a/doc/api/n-api.md b/doc/api/n-api.md index 91368267c42663..f5b07532d1d631 100644 --- a/doc/api/n-api.md +++ b/doc/api/n-api.md @@ -57,6 +57,8 @@ for the N-API C based functions exported by Node.js. These wrappers are not part of N-API, nor will they be maintained as part of Node.js. One such example is: [node-addon-api](https://github.com/nodejs/node-addon-api). +## Usage + In order to use the N-API functions, include the file [node_api.h](https://github.com/nodejs/node/blob/master/src/node_api.h) which is located in the src directory in the node development tree. @@ -65,6 +67,41 @@ For example: #include ``` +This will opt into the default `NAPI_VERSION` for the given release of Node.js. +In order to ensure compatibility with specific versions of N-API, the version +can be specified explicitly when including the header: + +```C +#define NAPI_VERSION 3 +#include +``` + +This restricts the N-API surface to just the functionality that was available in +the specified (and earlier) versions. + +Some of the N-API surface is considered experimental and requires explicit +opt-in to access those APIs: + +```C +#define NAPI_EXPERIMENTAL +#include +``` + +In this case the entire API surface, including any experimental APIs, will be +available to the module code. + +## N-API Version Matrix + +| | 1 | 2 | 3 | +|:-----:|:-------:|:--------:|:--------:| +| v4.x | | | | +| v6.x | | | v6.14.2* | +| v8.x | v8.0.0* | v8.10.0* | | +| v9.x | v9.0.0* | v9.3.0* | v9.11.0* | +| v10.x | | | v10.0.0 | + +\* Indicates that the N-API version was released as experimental + ## Basic N-API Data Types N-API exposes the following fundamental datatypes as abstractions that are @@ -337,6 +374,7 @@ It is intended only for logging purposes. #### napi_get_last_error_info ```C napi_status @@ -436,6 +474,7 @@ TypeError [ERR_ERROR_1] #### napi_throw ```C NODE_EXTERN napi_status napi_throw(napi_env env, napi_value error); @@ -451,6 +490,7 @@ This API throws the JavaScript Error provided. #### napi_throw_error ```C NODE_EXTERN napi_status napi_throw_error(napi_env env, @@ -469,6 +509,7 @@ This API throws a JavaScript Error with the text provided. #### napi_throw_type_error ```C NODE_EXTERN napi_status napi_throw_type_error(napi_env env, @@ -487,6 +528,7 @@ This API throws a JavaScript TypeError with the text provided. #### napi_throw_range_error ```C NODE_EXTERN napi_status napi_throw_range_error(napi_env env, @@ -506,6 +548,7 @@ This API throws a JavaScript RangeError with the text provided. #### napi_is_error ```C NODE_EXTERN napi_status napi_is_error(napi_env env, @@ -525,6 +568,7 @@ This API queries a `napi_value` to check if it represents an error object. #### napi_create_error ```C NODE_EXTERN napi_status napi_create_error(napi_env env, @@ -546,6 +590,7 @@ This API returns a JavaScript Error with the text provided. #### napi_create_type_error ```C NODE_EXTERN napi_status napi_create_type_error(napi_env env, @@ -568,6 +613,7 @@ This API returns a JavaScript TypeError with the text provided. #### napi_create_range_error ```C NODE_EXTERN napi_status napi_create_range_error(napi_env env, @@ -589,6 +635,7 @@ This API returns a JavaScript RangeError with the text provided. #### napi_get_and_clear_last_exception ```C napi_status napi_get_and_clear_last_exception(napi_env env, @@ -607,6 +654,7 @@ This API can be called even if there is a pending JavaScript exception. #### napi_is_exception_pending ```C napi_status napi_is_exception_pending(napi_env env, bool* result); @@ -624,7 +672,9 @@ This API can be called even if there is a pending JavaScript exception. #### napi_fatal_exception + ```C napi_status napi_fatal_exception(napi_env env, napi_value err); ``` @@ -643,6 +693,7 @@ thrown to immediately terminate the process. #### napi_fatal_error ```C NAPI_NO_RETURN void napi_fatal_error(const char* location, @@ -755,6 +806,7 @@ can only be called once. #### napi_open_handle_scope ```C NODE_EXTERN napi_status napi_open_handle_scope(napi_env env, @@ -770,6 +822,7 @@ This API open a new scope. #### napi_close_handle_scope ```C NODE_EXTERN napi_status napi_close_handle_scope(napi_env env, @@ -788,6 +841,7 @@ This API can be called even if there is a pending JavaScript exception. #### napi_open_escapable_handle_scope ```C NODE_EXTERN napi_status @@ -805,6 +859,7 @@ to the outer scope. #### napi_close_escapable_handle_scope ```C NODE_EXTERN napi_status @@ -824,6 +879,7 @@ This API can be called even if there is a pending JavaScript exception. #### napi_escape_handle ```C napi_status napi_escape_handle(napi_env env, @@ -888,6 +944,7 @@ individual count. #### napi_create_reference ```C NODE_EXTERN napi_status napi_create_reference(napi_env env, @@ -910,6 +967,7 @@ to the Object passed in. #### napi_delete_reference ```C NODE_EXTERN napi_status napi_delete_reference(napi_env env, napi_ref ref); @@ -927,6 +985,7 @@ This API can be called even if there is a pending JavaScript exception. #### napi_reference_ref ```C NODE_EXTERN napi_status napi_reference_ref(napi_env env, @@ -945,6 +1004,7 @@ passed in and returns the resulting reference count. #### napi_reference_unref ```C NODE_EXTERN napi_status napi_reference_unref(napi_env env, @@ -963,6 +1023,7 @@ passed in and returns the resulting reference count. #### napi_get_reference_value ```C NODE_EXTERN napi_status napi_get_reference_value(napi_env env, @@ -996,7 +1057,9 @@ should be freed up. #### napi_add_env_cleanup_hook + ```C NODE_EXTERN napi_status napi_add_env_cleanup_hook(napi_env env, void (*fun)(void* arg), @@ -1021,7 +1084,9 @@ is being torn down anyway. #### napi_remove_env_cleanup_hook + ```C NAPI_EXTERN napi_status napi_remove_env_cleanup_hook(napi_env env, void (*fun)(void* arg), @@ -1183,6 +1248,7 @@ of the [ECMAScript Language Specification][]. #### napi_create_array ```C napi_status napi_create_array(napi_env env, napi_value* result) @@ -1201,6 +1267,7 @@ ECMAScript Language Specification. #### napi_create_array_with_length ```C napi_status napi_create_array_with_length(napi_env env, @@ -1230,6 +1297,7 @@ ECMAScript Language Specification. #### napi_create_arraybuffer ```C napi_status napi_create_arraybuffer(napi_env env, @@ -1262,6 +1330,7 @@ of the ECMAScript Language Specification. #### napi_create_buffer ```C napi_status napi_create_buffer(napi_env env, @@ -1283,6 +1352,7 @@ fully-supported data structure, in most cases using a TypedArray will suffice. #### napi_create_buffer_copy ```C napi_status napi_create_buffer_copy(napi_env env, @@ -1308,6 +1378,7 @@ structure, in most cases using a TypedArray will suffice. #### napi_create_external ```C napi_status napi_create_external(napi_env env, @@ -1340,6 +1411,7 @@ additional properties. It is considered a distinct value type: calling #### napi_create_external_arraybuffer ```C napi_status @@ -1375,6 +1447,7 @@ of the ECMAScript Language Specification. #### napi_create_external_buffer ```C napi_status napi_create_external_buffer(napi_env env, @@ -1406,6 +1479,7 @@ structure, in most cases using a TypedArray will suffice. #### napi_create_function ```C napi_status napi_create_function(napi_env env, @@ -1439,6 +1513,7 @@ of the ECMAScript Language Specification. #### napi_create_object ```C napi_status napi_create_object(napi_env env, napi_value* result) @@ -1459,6 +1534,7 @@ ECMAScript Language Specification. #### napi_create_symbol ```C napi_status napi_create_symbol(napi_env env, @@ -1482,6 +1558,7 @@ of the ECMAScript Language Specification. #### napi_create_typedarray ```C napi_status napi_create_typedarray(napi_env env, @@ -1518,6 +1595,7 @@ of the ECMAScript Language Specification. #### napi_create_dataview ```C @@ -1552,6 +1630,7 @@ JavaScript DataView Objects are described in #### napi_create_int32 ```C napi_status napi_create_int32(napi_env env, int32_t value, napi_value* result) @@ -1573,6 +1652,7 @@ of the ECMAScript Language Specification. #### napi_create_uint32 ```C napi_status napi_create_uint32(napi_env env, uint32_t value, napi_value* result) @@ -1594,6 +1674,7 @@ of the ECMAScript Language Specification. #### napi_create_int64 ```C napi_status napi_create_int64(napi_env env, int64_t value, napi_value* result) @@ -1621,6 +1702,7 @@ outside the range of #### napi_create_double ```C napi_status napi_create_double(napi_env env, double value, napi_value* result) @@ -1642,6 +1724,7 @@ of the ECMAScript Language Specification. #### napi_create_string_latin1 ```C napi_status napi_create_string_latin1(napi_env env, @@ -1667,6 +1750,7 @@ of the ECMAScript Language Specification. #### napi_create_string_utf16 ```C napi_status napi_create_string_utf16(napi_env env, @@ -1692,6 +1776,7 @@ of the ECMAScript Language Specification. #### napi_create_string_utf8 ```C napi_status napi_create_string_utf8(napi_env env, @@ -1718,6 +1803,7 @@ of the ECMAScript Language Specification. #### napi_get_array_length ```C napi_status napi_get_array_length(napi_env env, @@ -1741,6 +1827,7 @@ of the ECMAScript Language Specification. #### napi_get_arraybuffer_info ```C napi_status napi_get_arraybuffer_info(napi_env env, @@ -1769,6 +1856,7 @@ callback as long as there are no calls to other APIs that might trigger a GC. #### napi_get_buffer_info ```C napi_status napi_get_buffer_info(napi_env env, @@ -1793,6 +1881,7 @@ lifetime is not guaranteed if it's managed by the VM. #### napi_get_prototype ```C napi_status napi_get_prototype(napi_env env, @@ -1811,6 +1900,7 @@ Returns `napi_ok` if the API succeeded. #### napi_get_typedarray_info ```C napi_status napi_get_typedarray_info(napi_env env, @@ -1841,6 +1931,7 @@ is managed by the VM #### napi_get_dataview_info ```C @@ -1868,6 +1959,7 @@ This API returns various properties of a DataView. #### napi_get_value_bool ```C napi_status napi_get_value_bool(napi_env env, napi_value value, bool* result) @@ -1887,6 +1979,7 @@ Boolean. #### napi_get_value_double ```C napi_status napi_get_value_double(napi_env env, @@ -1908,6 +2001,7 @@ Number. #### napi_get_value_external ```C napi_status napi_get_value_external(napi_env env, @@ -1928,6 +2022,7 @@ This API retrieves the external data pointer that was previously passed to #### napi_get_value_int32 ```C napi_status napi_get_value_int32(napi_env env, @@ -1955,6 +2050,7 @@ result to zero. #### napi_get_value_int64 ```C napi_status napi_get_value_int64(napi_env env, @@ -1984,6 +2080,7 @@ result to zero. #### napi_get_value_string_latin1 ```C napi_status napi_get_value_string_latin1(napi_env env, @@ -2011,6 +2108,7 @@ in. #### napi_get_value_string_utf8 ```C napi_status napi_get_value_string_utf8(napi_env env, @@ -2037,6 +2135,7 @@ This API returns the UTF8-encoded string corresponding the value passed in. #### napi_get_value_string_utf16 ```C napi_status napi_get_value_string_utf16(napi_env env, @@ -2063,6 +2162,7 @@ This API returns the UTF16-encoded string corresponding the value passed in. #### napi_get_value_uint32 ```C napi_status napi_get_value_uint32(napi_env env, @@ -2085,6 +2185,7 @@ This API returns the C primitive equivalent of the given `napi_value` as a #### napi_get_boolean ```C napi_status napi_get_boolean(napi_env env, bool value, napi_value* result) @@ -2103,6 +2204,7 @@ represent the given boolean value #### napi_get_global ```C napi_status napi_get_global(napi_env env, napi_value* result) @@ -2118,6 +2220,7 @@ This API returns the global Object. #### napi_get_null ```C napi_status napi_get_null(napi_env env, napi_value* result) @@ -2133,6 +2236,7 @@ This API returns the null Object. #### napi_get_undefined ```C napi_status napi_get_undefined(napi_env env, napi_value* result) @@ -2161,6 +2265,7 @@ These APIs support doing one of the following: ### napi_coerce_to_bool ```C napi_status napi_coerce_to_bool(napi_env env, @@ -2182,6 +2287,7 @@ This API can be re-entrant if getters are defined on the passed-in Object. ### napi_coerce_to_number ```C napi_status napi_coerce_to_number(napi_env env, @@ -2203,6 +2309,7 @@ This API can be re-entrant if getters are defined on the passed-in Object. ### napi_coerce_to_object ```C napi_status napi_coerce_to_object(napi_env env, @@ -2224,6 +2331,7 @@ This API can be re-entrant if getters are defined on the passed-in Object. ### napi_coerce_to_string ```C napi_status napi_coerce_to_string(napi_env env, @@ -2245,6 +2353,7 @@ This API can be re-entrant if getters are defined on the passed-in Object. ### napi_typeof ```C napi_status napi_typeof(napi_env env, napi_value value, napi_valuetype* result) @@ -2266,6 +2375,7 @@ If `value` has a type that is invalid, an error is returned. ### napi_instanceof ```C napi_status napi_instanceof(napi_env env, @@ -2291,6 +2401,7 @@ of the ECMAScript Language Specification. ### napi_is_array ```C napi_status napi_is_array(napi_env env, napi_value value, bool* result) @@ -2309,6 +2420,7 @@ of the ECMAScript Language Specification. ### napi_is_arraybuffer ```C napi_status napi_is_arraybuffer(napi_env env, napi_value value, bool* result) @@ -2325,6 +2437,7 @@ This API checks if the Object passed in is an array buffer. ### napi_is_buffer ```C napi_status napi_is_buffer(napi_env env, napi_value value, bool* result) @@ -2342,6 +2455,7 @@ This API checks if the Object passed in is a buffer. ### napi_is_error ```C napi_status napi_is_error(napi_env env, napi_value value, bool* result) @@ -2358,6 +2472,7 @@ This API checks if the Object passed in is an Error. ### napi_is_typedarray ```C napi_status napi_is_typedarray(napi_env env, napi_value value, bool* result) @@ -2374,6 +2489,7 @@ This API checks if the Object passed in is a typed array. ### napi_is_dataview ```C @@ -2391,6 +2507,7 @@ This API checks if the Object passed in is a DataView. ### napi_strict_equals ```C napi_status napi_strict_equals(napi_env env, @@ -2625,6 +2742,7 @@ this function is invoked. #### napi_get_property_names ```C napi_status napi_get_property_names(napi_env env, @@ -2646,6 +2764,7 @@ This API returns the array of properties for the Object passed in #### napi_set_property ```C napi_status napi_set_property(napi_env env, @@ -2666,6 +2785,7 @@ This API set a property on the Object passed in. #### napi_get_property ```C napi_status napi_get_property(napi_env env, @@ -2687,6 +2807,7 @@ This API gets the requested property from the Object passed in. #### napi_has_property ```C napi_status napi_has_property(napi_env env, @@ -2708,6 +2829,7 @@ This API checks if the Object passed in has the named property. #### napi_delete_property ```C napi_status napi_delete_property(napi_env env, @@ -2730,6 +2852,7 @@ This API attempts to delete the `key` own property from `object`. #### napi_has_own_property ```C napi_status napi_has_own_property(napi_env env, @@ -2753,6 +2876,7 @@ conversion between data types. #### napi_set_named_property ```C napi_status napi_set_named_property(napi_env env, @@ -2774,6 +2898,7 @@ created from the string passed in as `utf8Name` #### napi_get_named_property ```C napi_status napi_get_named_property(napi_env env, @@ -2795,6 +2920,7 @@ created from the string passed in as `utf8Name` #### napi_has_named_property ```C napi_status napi_has_named_property(napi_env env, @@ -2816,6 +2942,7 @@ created from the string passed in as `utf8Name` #### napi_set_element ```C napi_status napi_set_element(napi_env env, @@ -2836,6 +2963,7 @@ This API sets and element on the Object passed in. #### napi_get_element ```C napi_status napi_get_element(napi_env env, @@ -2856,6 +2984,7 @@ This API gets the element at the requested index. #### napi_has_element ```C napi_status napi_has_element(napi_env env, @@ -2877,6 +3006,7 @@ requested index. #### napi_delete_element ```C napi_status napi_delete_element(napi_env env, @@ -2898,6 +3028,7 @@ This API attempts to delete the specified `index` from `object`. #### napi_define_properties ```C napi_status napi_define_properties(napi_env env, @@ -2941,6 +3072,7 @@ function. ### napi_call_function ```C napi_status napi_call_function(napi_env env, @@ -3007,6 +3139,7 @@ if (status != napi_ok) return; ### napi_create_function ```C napi_status napi_create_function(napi_env env, @@ -3075,6 +3208,7 @@ responsible for creating the `.node` file. ### napi_get_cb_info ```C napi_status napi_get_cb_info(napi_env env, @@ -3105,6 +3239,7 @@ call like the arguments and the `this` pointer from a given callback info. ### napi_get_new_target ```C napi_status napi_get_new_target(napi_env env, @@ -3124,6 +3259,7 @@ callback is not a constructor call, the result is `NULL`. ### napi_new_instance ```C napi_status napi_new_instance(napi_env env, @@ -3219,6 +3355,7 @@ The reference must be freed once it is no longer needed. ### napi_define_class ```C napi_status napi_define_class(napi_env env, @@ -3275,6 +3412,7 @@ reference count is kept >= 1. ### napi_wrap ```C napi_status napi_wrap(napi_env env, @@ -3334,6 +3472,7 @@ another native instance with the object, use napi_remove_wrap() first. ### napi_unwrap ```C napi_status napi_unwrap(napi_env env, @@ -3359,6 +3498,7 @@ then by calling `napi_unwrap()` on the wrapper object. ### napi_remove_wrap ```C napi_status napi_remove_wrap(napi_env env, @@ -3429,6 +3569,7 @@ callback invocation, even when it was cancelled. ### napi_create_async_work ```C napi_status napi_delete_async_work(napi_env env, @@ -3495,6 +3637,7 @@ This API can be called even if there is a pending JavaScript exception. ### napi_queue_async_work ```C napi_status napi_queue_async_work(napi_env env, @@ -3512,6 +3655,7 @@ for execution. ### napi_cancel_async_work ```C napi_status napi_cancel_async_work(napi_env env, @@ -3541,6 +3685,7 @@ the runtime. ### napi_async_init ```C napi_status napi_async_init(napi_env env, @@ -3562,6 +3707,7 @@ Returns `napi_ok` if the API succeeded. ### napi_async_destroy ```C napi_status napi_async_destroy(napi_env env, @@ -3578,6 +3724,7 @@ This API can be called even if there is a pending JavaScript exception. ### napi_make_callback ```C NAPI_EXTERN napi_status napi_open_callback_scope(napi_env env, @@ -3649,6 +3797,7 @@ the required scope. ### *napi_close_callback_scope* ```C NAPI_EXTERN napi_status napi_close_callback_scope(napi_env env, @@ -3664,6 +3813,7 @@ This API can be called even if there is a pending JavaScript exception. ### napi_get_node_version ```C @@ -3692,6 +3842,7 @@ The returned buffer is statically allocated and does not need to be freed. ### napi_get_version ```C napi_status napi_get_version(napi_env env, @@ -3722,6 +3873,7 @@ support it: ### napi_adjust_external_memory ```C NAPI_EXTERN napi_status napi_adjust_external_memory(napi_env env, @@ -3801,6 +3953,7 @@ deferred = NULL; ### napi_create_promise ```C napi_status napi_create_promise(napi_env env, @@ -3821,6 +3974,7 @@ This API creates a deferred object and a JavaScript promise. ### napi_resolve_deferred ```C napi_status napi_resolve_deferred(napi_env env, @@ -3844,6 +3998,7 @@ The deferred object is freed upon successful completion. ### napi_reject_deferred ```C napi_status napi_reject_deferred(napi_env env, @@ -3867,6 +4022,7 @@ The deferred object is freed upon successful completion. ### napi_is_promise ```C napi_status napi_is_promise(napi_env env, @@ -3887,6 +4043,7 @@ underlying JavaScript engine. ### napi_run_script ```C NAPI_EXTERN napi_status napi_run_script(napi_env env, @@ -3906,6 +4063,7 @@ a specific `napi_env`. ### napi_get_uv_event_loop ```C NAPI_EXTERN napi_status napi_get_uv_event_loop(napi_env env, diff --git a/src/node_api.h b/src/node_api.h index 0868eabc5c6961..8a17c95afdde11 100644 --- a/src/node_api.h +++ b/src/node_api.h @@ -7,6 +7,16 @@ struct uv_loop_s; // Forward declaration. +#ifndef NAPI_VERSION +#ifdef NAPI_EXPERIMENTAL +// Use INT_MAX, this should only be consumed by the pre-processor anyway. +#define NAPI_VERSION 2147483647 +#else +// The baseline version for N-API +#define NAPI_VERSION 3 +#endif +#endif + #ifdef _WIN32 #ifdef BUILDING_NODE_EXTENSION #ifdef EXTERNAL_NAPI @@ -99,19 +109,10 @@ EXTERN_C_START NAPI_EXTERN void napi_module_register(napi_module* mod); -NAPI_EXTERN napi_status napi_add_env_cleanup_hook(napi_env env, - void (*fun)(void* arg), - void* arg); -NAPI_EXTERN napi_status napi_remove_env_cleanup_hook(napi_env env, - void (*fun)(void* arg), - void* arg); - NAPI_EXTERN napi_status napi_get_last_error_info(napi_env env, const napi_extended_error_info** result); -NAPI_EXTERN napi_status napi_fatal_exception(napi_env env, napi_value err); - NAPI_EXTERN NAPI_NO_RETURN void napi_fatal_error(const char* location, size_t location_len, const char* message, @@ -424,14 +425,6 @@ NAPI_EXTERN napi_status napi_escape_handle(napi_env env, napi_value escapee, napi_value* result); -NAPI_EXTERN napi_status napi_open_callback_scope(napi_env env, - napi_value resource_object, - napi_async_context context, - napi_callback_scope* result); - -NAPI_EXTERN napi_status napi_close_callback_scope(napi_env env, - napi_callback_scope scope); - // Methods to support error handling NAPI_EXTERN napi_status napi_throw(napi_env env, napi_value error); NAPI_EXTERN napi_status napi_throw_error(napi_env env, @@ -591,11 +584,38 @@ NAPI_EXTERN napi_status napi_run_script(napi_env env, napi_value script, napi_value* result); +#if NAPI_VERSION >= 2 + // Return the current libuv event loop for a given environment NAPI_EXTERN napi_status napi_get_uv_event_loop(napi_env env, struct uv_loop_s** loop); +#endif // NAPI_VERSION >= 2 + +#if NAPI_VERSION >= 3 + +NAPI_EXTERN napi_status napi_open_callback_scope(napi_env env, + napi_value resource_object, + napi_async_context context, + napi_callback_scope* result); + +NAPI_EXTERN napi_status napi_close_callback_scope(napi_env env, + napi_callback_scope scope); + +NAPI_EXTERN napi_status napi_fatal_exception(napi_env env, napi_value err); + +NAPI_EXTERN napi_status napi_add_env_cleanup_hook(napi_env env, + void (*fun)(void* arg), + void* arg); + +NAPI_EXTERN napi_status napi_remove_env_cleanup_hook(napi_env env, + void (*fun)(void* arg), + void* arg); + +#endif // NAPI_VERSION >= 3 + #ifdef NAPI_EXPERIMENTAL + // Calling into JS from other threads NAPI_EXTERN napi_status napi_create_threadsafe_function(napi_env env, @@ -633,6 +653,7 @@ NAPI_EXTERN napi_status napi_ref_threadsafe_function(napi_env env, napi_threadsafe_function func); #endif // NAPI_EXPERIMENTAL + EXTERN_C_END #endif // SRC_NODE_API_H_ diff --git a/tools/doc/common.js b/tools/doc/common.js index 553b52935fd709..1a734acde719af 100644 --- a/tools/doc/common.js +++ b/tools/doc/common.js @@ -24,6 +24,10 @@ function extractAndParseYAML(text) { meta.added = arrify(meta.added); } + if (meta.napiVersion) { + meta.napiVersion = arrify(meta.napiVersion); + } + if (meta.deprecated) { // Treat deprecated like added for consistency. meta.deprecated = arrify(meta.deprecated); diff --git a/tools/doc/html.js b/tools/doc/html.js index f373206760b5df..d8da7e5364e8e1 100644 --- a/tools/doc/html.js +++ b/tools/doc/html.js @@ -365,6 +365,10 @@ function parseYAML(text) { html.push(`${added.description}${deprecated.description}`); } + if (meta.napiVersion) { + html.push(`N-API version: ${meta.napiVersion.join(', ')}\n`); + } + html.push(''); return html.join('\n'); } From d6ffabc37f95aa4beda2f815e9fad6aed71e1802 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Mon, 21 Jan 2019 23:34:46 -0800 Subject: [PATCH 21/34] n-api: mark thread-safe function as stable Fixes: https://github.com/nodejs/node/issues/24249 PR-URL: https://github.com/nodejs/node/pull/25556 Backport-PR-URL: https://github.com/nodejs/node/pull/25648 Reviewed-By: James M Snell Reviewed-By: Colin Ihrig Reviewed-By: Michael Dawson --- doc/api/n-api.md | 22 +++++++++---------- src/node_api.h | 13 ++++++----- src/node_api_types.h | 16 +++++++------- src/node_version.h | 2 +- test/addons-napi/test_general/test.js | 4 ++-- .../test_threadsafe_function/binding.c | 1 - 6 files changed, 29 insertions(+), 29 deletions(-) diff --git a/doc/api/n-api.md b/doc/api/n-api.md index f5b07532d1d631..4e15e17603e30a 100644 --- a/doc/api/n-api.md +++ b/doc/api/n-api.md @@ -171,7 +171,7 @@ This is an opaque pointer that is used to represent a JavaScript value. ### napi_threadsafe_function -> Stability: 1 - Experimental +> Stability: 2 - Stable This is an opaque pointer that represents a JavaScript function which can be called asynchronously from multiple threads via @@ -179,7 +179,7 @@ called asynchronously from multiple threads via ### napi_threadsafe_function_release_mode -> Stability: 1 - Experimental +> Stability: 2 - Stable A value to be given to `napi_release_threadsafe_function()` to indicate whether the thread-safe function is to be closed immediately (`napi_tsfn_abort`) or @@ -194,7 +194,7 @@ typedef enum { ### napi_threadsafe_function_call_mode -> Stability: 1 - Experimental +> Stability: 2 - Stable A value to be given to `napi_call_threadsafe_function()` to indicate whether the call should block whenever the queue associated with the thread-safe @@ -285,7 +285,7 @@ typedef void (*napi_async_complete_callback)(napi_env env, #### napi_threadsafe_function_call_js -> Stability: 1 - Experimental +> Stability: 2 - Stable Function pointer used with asynchronous thread-safe function calls. The callback will be called on the main thread. Its purpose is to use a data item arriving @@ -4184,7 +4184,7 @@ prevent the event loop from exiting. The APIs `napi_ref_threadsafe_function` and ### napi_create_threadsafe_function -> Stability: 1 - Experimental +> Stability: 2 - Stable ```C NAPI_EXTERN napi_status @@ -4230,7 +4230,7 @@ parameters and with `undefined` as its `this` value. > Stability: 2 - Stable ```C NAPI_EXTERN napi_status @@ -4248,7 +4248,7 @@ This API may be called from any thread which makes use of `func`. > Stability: 2 - Stable ```C NAPI_EXTERN napi_status @@ -4276,7 +4276,7 @@ This API may be called from any thread which makes use of `func`. > Stability: 2 - Stable ```C NAPI_EXTERN napi_status @@ -4298,7 +4298,7 @@ This API may be called from any thread which will start making use of `func`. > Stability: 2 - Stable ```C NAPI_EXTERN napi_status @@ -4326,7 +4326,7 @@ This API may be called from any thread which will stop making use of `func`. > Stability: 2 - Stable ```C NAPI_EXTERN napi_status @@ -4347,7 +4347,7 @@ This API may only be called from the main thread. > Stability: 2 - Stable ```C NAPI_EXTERN napi_status diff --git a/doc/changelogs/CHANGELOG_V8.md b/doc/changelogs/CHANGELOG_V8.md index 6c503420e538a3..5aebf0e7c6cdf8 100644 --- a/doc/changelogs/CHANGELOG_V8.md +++ b/doc/changelogs/CHANGELOG_V8.md @@ -10,6 +10,7 @@ +8.16.0
8.15.1
8.15.0
8.14.1
@@ -62,6 +63,51 @@ [Node.js Long Term Support Plan](https://github.com/nodejs/LTS) and will be supported actively until April 2019 and maintained until December 2019. + +## 2019-04-16, Version 8.16.0 'Carbon' (LTS), @MylesBorins + +### Notable Changes + +* **n-api**: + - add API for asynchronous functions (Gabriel Schulhof) [#17887](https://github.com/nodejs/node/pull/17887) + - mark thread-safe function as stable (Gabriel Schulhof) [#25556](https://github.com/nodejs/node/pull/25556) + +### Commits + +* [[`705935d620`](https://github.com/nodejs/node/commit/705935d620)] - **assert**: fix backport regression (Ruben Bridgewater) [#27202](https://github.com/nodejs/node/pull/27202) +* [[`c07ba9681f`](https://github.com/nodejs/node/commit/c07ba9681f)] - **build**: skip cctest on Windows shared lib build (Yihong Wang) [#21228](https://github.com/nodejs/node/pull/21228) +* [[`63522886ea`](https://github.com/nodejs/node/commit/63522886ea)] - **build**: add loader path to rpath for cctest (Sam Ruby) [#23168](https://github.com/nodejs/node/pull/23168) +* [[`e9369073d9`](https://github.com/nodejs/node/commit/e9369073d9)] - **build**: set `-blibpath:` for AIX (Richard Lau) [#25447](https://github.com/nodejs/node/pull/25447) +* [[`97cc0fc51d`](https://github.com/nodejs/node/commit/97cc0fc51d)] - **deps**: V8: cherry-pick 3cc6919 (Farazmand) [#25874](https://github.com/nodejs/node/pull/25874) +* [[`a1aff28fba`](https://github.com/nodejs/node/commit/a1aff28fba)] - **deps**: cherry-pick 525b396 from V8 upstream (Peter Marshall) [#25041](https://github.com/nodejs/node/pull/25041) +* [[`6b7cccc88a`](https://github.com/nodejs/node/commit/6b7cccc88a)] - **doc**: fix optional parameters in n-api.md (Lars-Magnus Skog) [#22998](https://github.com/nodejs/node/pull/22998) +* [[`b17819db3d`](https://github.com/nodejs/node/commit/b17819db3d)] - **doc**: update the http.request.setTimeout docs to be accurate (James Bunton) [#25123](https://github.com/nodejs/node/pull/25123) +* [[`ac9b8f7645`](https://github.com/nodejs/node/commit/ac9b8f7645)] - **http**: fix error check in `Execute()` (Brian White) [#24738](https://github.com/nodejs/node/pull/24738) +* [[`1d862610f8`](https://github.com/nodejs/node/commit/1d862610f8)] - **http**: attach reused parser to correct domain (Julien Gilli) [#25459](https://github.com/nodejs/node/pull/25459) +* [[`d3de1ed653`](https://github.com/nodejs/node/commit/d3de1ed653)] - **n-api**: improve performance creating strings (Anthony Tuininga) [#26439](https://github.com/nodejs/node/pull/26439) +* [[`2b2ad96ef2`](https://github.com/nodejs/node/commit/2b2ad96ef2)] - **n-api**: finalize during second-pass callback (Gabriel Schulhof) [#25992](https://github.com/nodejs/node/pull/25992) +* [[`d6ffabc37f`](https://github.com/nodejs/node/commit/d6ffabc37f)] - **(SEMVER-MINOR)** **n-api**: mark thread-safe function as stable (Gabriel Schulhof) [#25556](https://github.com/nodejs/node/pull/25556) +* [[`44609d1274`](https://github.com/nodejs/node/commit/44609d1274)] - **n-api**: restrict exports by version (Kyle Farnung) [#19962](https://github.com/nodejs/node/pull/19962) +* [[`fe4328252a`](https://github.com/nodejs/node/commit/fe4328252a)] - **n-api**: add missing handle scopes (Daniel Bevenius) [#24011](https://github.com/nodejs/node/pull/24011) +* [[`902b07959f`](https://github.com/nodejs/node/commit/902b07959f)] - **n-api**: clean up thread-safe function (Gabriel Schulhof) [#22259](https://github.com/nodejs/node/pull/22259) +* [[`09b88aabb3`](https://github.com/nodejs/node/commit/09b88aabb3)] - **n-api**: remove idle\_running from TsFn (Lars-Magnus Skog) [#22520](https://github.com/nodejs/node/pull/22520) +* [[`367505940a`](https://github.com/nodejs/node/commit/367505940a)] - **n-api**: guard against cond null dereference (Gabriel Schulhof) [#21871](https://github.com/nodejs/node/pull/21871) +* [[`c5a11dc58e`](https://github.com/nodejs/node/commit/c5a11dc58e)] - **n-api**: fix compiler warning (cjihrig) [#21597](https://github.com/nodejs/node/pull/21597) +* [[`759a0180b5`](https://github.com/nodejs/node/commit/759a0180b5)] - **(SEMVER-MINOR)** **n-api**: add API for asynchronous functions (Gabriel Schulhof) [#17887](https://github.com/nodejs/node/pull/17887) +* [[`ea5628e77a`](https://github.com/nodejs/node/commit/ea5628e77a)] - **process**: allow reading from stdout/stderr sockets (Anna Henningsen) [#23053](https://github.com/nodejs/node/pull/23053) +* [[`67b6e0d19c`](https://github.com/nodejs/node/commit/67b6e0d19c)] - **src**: fix may be uninitialized warning in n-api (Michael Dawson) [#21898](https://github.com/nodejs/node/pull/21898) +* [[`eaf474cc5d`](https://github.com/nodejs/node/commit/eaf474cc5d)] - **test**: shared lib build doesn't handle SIGPIPE (Yihong Wang) [#19211](https://github.com/nodejs/node/pull/19211) +* [[`3128cb7da6`](https://github.com/nodejs/node/commit/3128cb7da6)] - **test**: avoid running fsync on directory on AIX (John Barboza) [#21298](https://github.com/nodejs/node/pull/21298) +* [[`b4c5435a46`](https://github.com/nodejs/node/commit/b4c5435a46)] - **test**: add process.stdin.end() TTY regression test (Matteo Collina) [#23051](https://github.com/nodejs/node/pull/23051) +* [[`c56f3edb10`](https://github.com/nodejs/node/commit/c56f3edb10)] - **test**: add stdin writable regression test (Anna Henningsen) [#23053](https://github.com/nodejs/node/pull/23053) +* [[`f6ff8c51bc`](https://github.com/nodejs/node/commit/f6ff8c51bc)] - **test**: fix module loading error for AIX 7.1 (Richard Lau) [#25418](https://github.com/nodejs/node/pull/25418) +* [[`d4b6643ac3`](https://github.com/nodejs/node/commit/d4b6643ac3)] - **test**: mark test-cli-node-options flaky on arm (Rich Trott) [#25032](https://github.com/nodejs/node/pull/25032) +* [[`60db455961`](https://github.com/nodejs/node/commit/60db455961)] - **test**: mark test\_threadsafe\_function/test as flaky (Gireesh Punathil) [#24714](https://github.com/nodejs/node/pull/24714) +* [[`fbafe8d311`](https://github.com/nodejs/node/commit/fbafe8d311)] - **test**: fix test-repl-envvars (Anna Henningsen) [#25226](https://github.com/nodejs/node/pull/25226) +* [[`7573b55a15`](https://github.com/nodejs/node/commit/7573b55a15)] - **tls**: fix legacy SecurePair clienthello race window (Ben Noordhuis) [#26452](https://github.com/nodejs/node/pull/26452) +* [[`91620b8bd6`](https://github.com/nodejs/node/commit/91620b8bd6)] - **tls**: fix legacy SecurePair session resumption (Ben Noordhuis) [#26452](https://github.com/nodejs/node/pull/26452) +* [[`1a9582b7a6`](https://github.com/nodejs/node/commit/1a9582b7a6)] - **tools**: allow input for TTY tests (Anna Henningsen) [#23053](https://github.com/nodejs/node/pull/23053) + ## 2019-02-28, Version 8.15.1 'Carbon' (LTS), @rvagg diff --git a/src/node_version.h b/src/node_version.h index 8d5423b1c6479f..8c57b7de65d6a7 100644 --- a/src/node_version.h +++ b/src/node_version.h @@ -23,13 +23,13 @@ #define SRC_NODE_VERSION_H_ #define NODE_MAJOR_VERSION 8 -#define NODE_MINOR_VERSION 15 -#define NODE_PATCH_VERSION 2 +#define NODE_MINOR_VERSION 16 +#define NODE_PATCH_VERSION 0 #define NODE_VERSION_IS_LTS 1 #define NODE_VERSION_LTS_CODENAME "Carbon" -#define NODE_VERSION_IS_RELEASE 0 +#define NODE_VERSION_IS_RELEASE 1 #ifndef NODE_STRINGIFY #define NODE_STRINGIFY(n) NODE_STRINGIFY_HELPER(n)