diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dd350ea6e37a3..dcd06779f80ba5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,8 @@ release. -8.15.1
+8.16.0
+8.15.1
8.15.0
8.14.1
8.14.0
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' ], diff --git a/deps/v8/include/v8-version.h b/deps/v8/include/v8-version.h index 62c81f2ca057ad..db93e49041c665 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 77 // 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(); diff --git a/deps/v8/src/regexp/ppc/regexp-macro-assembler-ppc.cc b/deps/v8/src/regexp/ppc/regexp-macro-assembler-ppc.cc index a1425b4372ec39..260e98bcc2577f 100644 --- a/deps/v8/src/regexp/ppc/regexp-macro-assembler-ppc.cc +++ b/deps/v8/src/regexp/ppc/regexp-macro-assembler-ppc.cc @@ -142,8 +142,13 @@ int RegExpMacroAssemblerPPC::stack_limit_slack() { void RegExpMacroAssemblerPPC::AdvanceCurrentPosition(int by) { if (by != 0) { - __ addi(current_input_offset(), current_input_offset(), - Operand(by * char_size())); + if (is_int16(by * char_size())) { + __ addi(current_input_offset(), current_input_offset(), + Operand(by * char_size())); + } else { + __ mov(r0, Operand(by * char_size())); + __ add(current_input_offset(), r0, current_input_offset()); + } } } @@ -1270,7 +1275,12 @@ void RegExpMacroAssemblerPPC::LoadCurrentCharacterUnchecked(int cp_offset, Register offset = current_input_offset(); if (cp_offset != 0) { // r25 is not being used to store the capture start index at this point. - __ addi(r25, current_input_offset(), Operand(cp_offset * char_size())); + if (is_int16(cp_offset * char_size())) { + __ addi(r25, current_input_offset(), Operand(cp_offset * char_size())); + } else { + __ mov(r25, Operand(cp_offset * char_size())); + __ add(r25, r25, current_input_offset()); + } offset = r25; } // The lwz, stw, lhz, sth instructions can do unaligned accesses, if the CPU diff --git a/doc/api/errors.md b/doc/api/errors.md index 53ef8395ac7065..a9319158387a67 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 @@ -1147,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/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`. diff --git a/doc/api/n-api.md b/doc/api/n-api.md index 1e43e4f224d9f0..cb322345d534f0 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 @@ -90,7 +127,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 +169,43 @@ not allowed. ### napi_value This is an opaque pointer that is used to represent a JavaScript value. +### napi_threadsafe_function + +> Stability: 2 - Stable + +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: 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 +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: 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 +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 +283,43 @@ typedef void (*napi_async_complete_callback)(napi_env env, void* data); ``` +#### napi_threadsafe_function_call_js + +> 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 +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. @@ -259,6 +374,7 @@ It is intended only for logging purposes. #### napi_get_last_error_info ```C napi_status @@ -358,6 +474,7 @@ TypeError [ERR_ERROR_1] #### napi_throw ```C NODE_EXTERN napi_status napi_throw(napi_env env, napi_value error); @@ -373,6 +490,7 @@ This API throws the JavaScript Error provided. #### napi_throw_error ```C NODE_EXTERN napi_status napi_throw_error(napi_env env, @@ -391,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, @@ -409,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, @@ -428,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, @@ -447,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, @@ -468,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, @@ -490,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, @@ -511,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, @@ -529,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); @@ -546,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); ``` @@ -565,6 +693,7 @@ thrown to immediately terminate the process. #### napi_fatal_error ```C NAPI_NO_RETURN void napi_fatal_error(const char* location, @@ -677,6 +806,7 @@ can only be called once. #### napi_open_handle_scope ```C NODE_EXTERN napi_status napi_open_handle_scope(napi_env env, @@ -692,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, @@ -710,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 @@ -727,6 +859,7 @@ to the outer scope. #### napi_close_escapable_handle_scope ```C NODE_EXTERN napi_status @@ -746,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, @@ -810,6 +944,7 @@ individual count. #### napi_create_reference ```C NODE_EXTERN napi_status napi_create_reference(napi_env env, @@ -832,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); @@ -849,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, @@ -867,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, @@ -885,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, @@ -918,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), @@ -943,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), @@ -1105,6 +1248,7 @@ of the [ECMAScript Language Specification][]. #### napi_create_array ```C napi_status napi_create_array(napi_env env, napi_value* result) @@ -1123,6 +1267,7 @@ ECMAScript Language Specification. #### napi_create_array_with_length ```C napi_status napi_create_array_with_length(napi_env env, @@ -1152,6 +1297,7 @@ ECMAScript Language Specification. #### napi_create_arraybuffer ```C napi_status napi_create_arraybuffer(napi_env env, @@ -1184,6 +1330,7 @@ of the ECMAScript Language Specification. #### napi_create_buffer ```C napi_status napi_create_buffer(napi_env env, @@ -1205,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, @@ -1230,6 +1378,7 @@ structure, in most cases using a TypedArray will suffice. #### napi_create_external ```C napi_status napi_create_external(napi_env env, @@ -1262,6 +1411,7 @@ additional properties. It is considered a distinct value type: calling #### napi_create_external_arraybuffer ```C napi_status @@ -1297,6 +1447,7 @@ of the ECMAScript Language Specification. #### napi_create_external_buffer ```C napi_status napi_create_external_buffer(napi_env env, @@ -1328,6 +1479,7 @@ structure, in most cases using a TypedArray will suffice. #### napi_create_function ```C napi_status napi_create_function(napi_env env, @@ -1361,6 +1513,7 @@ of the ECMAScript Language Specification. #### napi_create_object ```C napi_status napi_create_object(napi_env env, napi_value* result) @@ -1381,6 +1534,7 @@ ECMAScript Language Specification. #### napi_create_symbol ```C napi_status napi_create_symbol(napi_env env, @@ -1404,6 +1558,7 @@ of the ECMAScript Language Specification. #### napi_create_typedarray ```C napi_status napi_create_typedarray(napi_env env, @@ -1440,6 +1595,7 @@ of the ECMAScript Language Specification. #### napi_create_dataview ```C @@ -1474,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) @@ -1495,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) @@ -1516,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) @@ -1543,6 +1702,7 @@ outside the range of #### napi_create_double ```C napi_status napi_create_double(napi_env env, double value, napi_value* result) @@ -1564,6 +1724,7 @@ of the ECMAScript Language Specification. #### napi_create_string_latin1 ```C napi_status napi_create_string_latin1(napi_env env, @@ -1589,6 +1750,7 @@ of the ECMAScript Language Specification. #### napi_create_string_utf16 ```C napi_status napi_create_string_utf16(napi_env env, @@ -1614,6 +1776,7 @@ of the ECMAScript Language Specification. #### napi_create_string_utf8 ```C napi_status napi_create_string_utf8(napi_env env, @@ -1640,6 +1803,7 @@ of the ECMAScript Language Specification. #### napi_get_array_length ```C napi_status napi_get_array_length(napi_env env, @@ -1663,6 +1827,7 @@ of the ECMAScript Language Specification. #### napi_get_arraybuffer_info ```C napi_status napi_get_arraybuffer_info(napi_env env, @@ -1691,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, @@ -1715,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, @@ -1733,6 +1900,7 @@ Returns `napi_ok` if the API succeeded. #### napi_get_typedarray_info ```C napi_status napi_get_typedarray_info(napi_env env, @@ -1763,6 +1931,7 @@ is managed by the VM #### napi_get_dataview_info ```C @@ -1790,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) @@ -1809,6 +1979,7 @@ Boolean. #### napi_get_value_double ```C napi_status napi_get_value_double(napi_env env, @@ -1830,6 +2001,7 @@ Number. #### napi_get_value_external ```C napi_status napi_get_value_external(napi_env env, @@ -1850,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, @@ -1877,6 +2050,7 @@ result to zero. #### napi_get_value_int64 ```C napi_status napi_get_value_int64(napi_env env, @@ -1906,6 +2080,7 @@ result to zero. #### napi_get_value_string_latin1 ```C napi_status napi_get_value_string_latin1(napi_env env, @@ -1933,6 +2108,7 @@ in. #### napi_get_value_string_utf8 ```C napi_status napi_get_value_string_utf8(napi_env env, @@ -1959,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, @@ -1985,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, @@ -2007,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) @@ -2025,6 +2204,7 @@ represent the given boolean value #### napi_get_global ```C napi_status napi_get_global(napi_env env, napi_value* result) @@ -2040,6 +2220,7 @@ This API returns the global Object. #### napi_get_null ```C napi_status napi_get_null(napi_env env, napi_value* result) @@ -2055,6 +2236,7 @@ This API returns the null Object. #### napi_get_undefined ```C napi_status napi_get_undefined(napi_env env, napi_value* result) @@ -2083,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, @@ -2104,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, @@ -2125,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, @@ -2146,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, @@ -2167,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) @@ -2188,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, @@ -2213,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) @@ -2231,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) @@ -2247,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) @@ -2264,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) @@ -2280,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) @@ -2296,6 +2489,7 @@ This API checks if the Object passed in is a typed array. ### napi_is_dataview ```C @@ -2313,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, @@ -2547,6 +2742,7 @@ this function is invoked. #### napi_get_property_names ```C napi_status napi_get_property_names(napi_env env, @@ -2568,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, @@ -2588,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, @@ -2609,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, @@ -2630,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, @@ -2652,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, @@ -2675,6 +2876,7 @@ conversion between data types. #### napi_set_named_property ```C napi_status napi_set_named_property(napi_env env, @@ -2696,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, @@ -2717,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, @@ -2738,6 +2942,7 @@ created from the string passed in as `utf8Name` #### napi_set_element ```C napi_status napi_set_element(napi_env env, @@ -2758,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, @@ -2778,6 +2984,7 @@ This API gets the element at the requested index. #### napi_has_element ```C napi_status napi_has_element(napi_env env, @@ -2799,6 +3006,7 @@ requested index. #### napi_delete_element ```C napi_status napi_delete_element(napi_env env, @@ -2820,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, @@ -2863,6 +3072,7 @@ function. ### napi_call_function ```C napi_status napi_call_function(napi_env env, @@ -2929,6 +3139,7 @@ if (status != napi_ok) return; ### napi_create_function ```C napi_status napi_create_function(napi_env env, @@ -2997,6 +3208,7 @@ responsible for creating the `.node` file. ### napi_get_cb_info ```C napi_status napi_get_cb_info(napi_env env, @@ -3027,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, @@ -3046,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, @@ -3141,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, @@ -3197,6 +3412,7 @@ reference count is kept >= 1. ### napi_wrap ```C napi_status napi_wrap(napi_env env, @@ -3256,6 +3472,7 @@ another native instance with the object, use napi_remove_wrap() first. ### napi_unwrap ```C napi_status napi_unwrap(napi_env env, @@ -3281,6 +3498,7 @@ then by calling `napi_unwrap()` on the wrapper object. ### napi_remove_wrap ```C napi_status napi_remove_wrap(napi_env env, @@ -3351,6 +3569,7 @@ callback invocation, even when it was cancelled. ### napi_create_async_work ```C napi_status napi_delete_async_work(napi_env env, @@ -3417,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, @@ -3434,6 +3655,7 @@ for execution. ### napi_cancel_async_work ```C napi_status napi_cancel_async_work(napi_env env, @@ -3463,6 +3685,7 @@ the runtime. ### napi_async_init ```C napi_status napi_async_init(napi_env env, @@ -3484,6 +3707,7 @@ Returns `napi_ok` if the API succeeded. ### napi_async_destroy ```C napi_status napi_async_destroy(napi_env env, @@ -3500,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, @@ -3571,6 +3797,7 @@ the required scope. ### *napi_close_callback_scope* ```C NAPI_EXTERN napi_status napi_close_callback_scope(napi_env env, @@ -3586,6 +3813,7 @@ This API can be called even if there is a pending JavaScript exception. ### napi_get_node_version ```C @@ -3614,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, @@ -3644,6 +3873,7 @@ support it: ### napi_adjust_external_memory ```C NAPI_EXTERN napi_status napi_adjust_external_memory(napi_env env, @@ -3723,6 +3953,7 @@ deferred = NULL; ### napi_create_promise ```C napi_status napi_create_promise(napi_env env, @@ -3743,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, @@ -3766,6 +3998,7 @@ The deferred object is freed upon successful completion. ### napi_reject_deferred ```C napi_status napi_reject_deferred(napi_env env, @@ -3789,6 +4022,7 @@ The deferred object is freed upon successful completion. ### napi_is_promise ```C napi_status napi_is_promise(napi_env env, @@ -3809,6 +4043,7 @@ underlying JavaScript engine. ### napi_run_script ```C NAPI_EXTERN napi_status napi_run_script(napi_env env, @@ -3828,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, @@ -3837,6 +4073,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: 2 - Stable + + +```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`: 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`. +- `[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: 2 - Stable + + +```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: 2 - Stable + + +```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: 2 - Stable + + +```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: 2 - Stable + + +```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: 2 - Stable + + +```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: 2 - Stable + + +```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 +4425,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/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/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/lib/_http_client.js b/lib/_http_client.js index 7b5798fe021a87..0dc8da0f4f6c5e 100644 --- a/lib/_http_client.js +++ b/lib/_http_client.js @@ -509,12 +509,6 @@ function parserOnIncomingClient(res, shouldKeepAlive) { var socket = this.socket; var req = socket._httpMessage; - // propagate "domain" setting... - if (req.domain && !res.domain) { - debug('setting "res.domain"'); - res.domain = req.domain; - } - debug('AGENT incoming response!'); if (req.res) { @@ -629,6 +623,9 @@ function tickOnSocket(req, socket) { req.socket = socket; req.connection = socket; parser.reinitialize(HTTPParser.RESPONSE, parser[is_reused_symbol]); + if (process.domain) { + process.domain.add(parser); + } parser.socket = socket; parser.incoming = null; parser.outgoing = req; diff --git a/lib/_tls_legacy.js b/lib/_tls_legacy.js index fc2fb6db5d12ae..7b9e34bb847c31 100644 --- a/lib/_tls_legacy.js +++ b/lib/_tls_legacy.js @@ -659,6 +659,10 @@ function onclienthello(hello) { if (err) return self.socket.destroy(err); setImmediate(function() { + // SecurePair might have been destroyed in the time window + // between callback() and this function. + if (!self.ssl) return; + self.ssl.loadSession(session); self.ssl.endParser(); @@ -691,8 +695,9 @@ function onnewsession(key, session) { return; once = true; - if (self.ssl) - self.ssl.newSessionDone(); + // Cycle data + self.cleartext.read(0); + self.encrypted.read(0); } } diff --git a/lib/assert.js b/lib/assert.js index e29a381acb746d..4cab918e6a74e9 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -720,7 +720,8 @@ function getActual(block) { async function waitForActual(block) { if (typeof block !== 'function') { - throw new errors.ERR_INVALID_ARG_TYPE('block', 'Function', block); + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'block', 'Function', + block); } // Return a rejected promise if `block` throws synchronously. 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/node.gyp b/node.gyp index ed20a490d9ee1d..91a0590993b973 100644 --- a/node.gyp +++ b/node.gyp @@ -178,6 +178,9 @@ 'sources': [ 'src/node_main.cc' ], + 'includes': [ + 'node.gypi' + ], 'include_dirs': [ 'src', 'deps/v8/include', @@ -195,9 +198,6 @@ }], [ 'node_intermediate_lib_type=="static_library" and ' 'node_shared=="false"', { - 'includes': [ - 'node.gypi' - ], 'xcode_settings': { 'OTHER_LDFLAGS': [ '-Wl,-force_load,<(PRODUCT_DIR)/<(STATIC_LIB_PREFIX)' @@ -439,22 +439,8 @@ ], }], ], - 'defines!': [ - 'NODE_PLATFORM="win"', - ], - 'defines': [ - 'FD_SETSIZE=1024', - # we need to use node's preferred "win32" rather than gyp's preferred "win" - 'NODE_PLATFORM="win32"', - # Stop from defining macros that conflict with - # std::min() and std::max(). We don't use (much) - # but we still inherit it from uv.h. - 'NOMINMAX', - '_UNICODE=1', - ], 'libraries': [ '-lpsapi.lib' ] }, { # POSIX - 'defines': [ '__POSIX__' ], 'sources': [ 'src/backtrace_posix.cc' ], }], [ 'node_use_etw=="true"', { @@ -956,6 +942,15 @@ ['OS=="solaris"', { 'ldflags': [ '-I<(SHARED_INTERMEDIATE_DIR)' ] }], + [ 'node_shared=="true"', { + 'xcode_settings': { + 'OTHER_LDFLAGS': [ '-Wl,-rpath,@loader_path', ], + }, + }], + # Skip cctest while building shared lib node for Windows + [ 'OS=="win" and node_shared=="true"', { + 'type': 'none', + }], ], } ], # end targets diff --git a/node.gypi b/node.gypi index 82953ee9ff9af4..3faffdfff3366f 100644 --- a/node.gypi +++ b/node.gypi @@ -37,6 +37,24 @@ 'NODE_SHARED_MODE', ], }], + [ 'OS=="win"', { + 'defines!': [ + 'NODE_PLATFORM="win"', + ], + 'defines': [ + 'FD_SETSIZE=1024', + # we need to use node's preferred "win32" rather than gyp's preferred "win" + 'NODE_PLATFORM="win32"', + # Stop from defining macros that conflict with + # std::min() and std::max(). We don't use (much) + # but we still inherit it from uv.h. + 'NOMINMAX', + '_UNICODE=1', + ], + }, { # POSIX + 'defines': [ '__POSIX__' ], + }], + [ 'node_enable_d8=="true"', { 'dependencies': [ 'deps/v8/src/d8.gyp:d8' ], }], diff --git a/src/env.h b/src/env.h index e378869b4ccdcb..b47d55d87b3e69 100644 --- a/src/env.h +++ b/src/env.h @@ -216,7 +216,6 @@ class ModuleWrap; V(onheaders_string, "onheaders") \ V(onmessage_string, "onmessage") \ V(onnewsession_string, "onnewsession") \ - V(onnewsessiondone_string, "onnewsessiondone") \ V(onocspresponse_string, "onocspresponse") \ V(ongoawaydata_string, "ongoawaydata") \ V(onorigin_string, "onorigin") \ diff --git a/src/node_api.cc b/src/node_api.cc index 61e9f6b4b00279..4d996f97a58dde 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" @@ -447,10 +448,25 @@ class Reference : private Finalizer { } private: + // The N-API finalizer callback may make calls into the engine. V8's heap is + // not in a consistent state during the weak callback, and therefore it does + // not support calls back into it. However, it provides a mechanism for adding + // a finalizer which may make calls back into the engine by allowing us to + // attach such a second-pass finalizer from the first pass finalizer. Thus, + // we do that here to ensure that the N-API finalizer callback is free to call + // into the engine. static void FinalizeCallback(const v8::WeakCallbackInfo& data) { Reference* reference = data.GetParameter(); + + // The reference must be reset during the first pass. reference->_persistent.Reset(); + data.SetSecondPassCallback(SecondPassCallback); + } + + static void SecondPassCallback(const v8::WeakCallbackInfo& data) { + Reference* reference = data.GetParameter(); + // Check before calling the finalize callback, because the callback might // delete it. bool delete_self = reference->_delete_self; @@ -855,6 +871,341 @@ napi_status ConcludeDeferred(napi_env env, return GET_RETURN_STATUS(env); } +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); + } + + ~ThreadSafeFunction() { + 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() { + ThreadSafeFunction* 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 { + ThreadSafeFunction* ts_fn = + node::ContainerOf(&ThreadSafeFunction::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 = nullptr; + 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; + 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) { + v8::HandleScope scope(env->isolate); + 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)); + v8::HandleScope scope(ts_fn->env->isolate); + 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 @@ -947,7 +1298,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 +1332,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); @@ -1639,12 +1993,15 @@ napi_status napi_create_string_latin1(napi_env env, napi_value* result) { CHECK_ENV(env); CHECK_ARG(env, result); + RETURN_STATUS_IF_FALSE(env, + (length == NAPI_AUTO_LENGTH) || length <= INT_MAX, + napi_invalid_arg); auto isolate = env->isolate; auto str_maybe = v8::String::NewFromOneByte(isolate, reinterpret_cast(str), - v8::NewStringType::kInternalized, + v8::NewStringType::kNormal, length); CHECK_MAYBE_EMPTY(env, str_maybe, napi_generic_failure); @@ -1658,11 +2015,18 @@ napi_status napi_create_string_utf8(napi_env env, napi_value* result) { CHECK_ENV(env); CHECK_ARG(env, result); + RETURN_STATUS_IF_FALSE(env, + (length == NAPI_AUTO_LENGTH) || length <= INT_MAX, + napi_invalid_arg); - v8::Local s; - CHECK_NEW_FROM_UTF8_LEN(env, s, str, length); - - *result = v8impl::JsValueFromV8LocalValue(s); + auto isolate = env->isolate; + auto str_maybe = + v8::String::NewFromUtf8(isolate, + str, + v8::NewStringType::kNormal, + static_cast(length)); + CHECK_MAYBE_EMPTY(env, str_maybe, napi_generic_failure); + *result = v8impl::JsValueFromV8LocalValue(str_maybe.ToLocalChecked()); return napi_clear_last_error(env); } @@ -1672,12 +2036,15 @@ napi_status napi_create_string_utf16(napi_env env, napi_value* result) { CHECK_ENV(env); CHECK_ARG(env, result); + RETURN_STATUS_IF_FALSE(env, + (length == NAPI_AUTO_LENGTH) || length <= INT_MAX, + napi_invalid_arg); auto isolate = env->isolate; auto str_maybe = v8::String::NewFromTwoByte(isolate, reinterpret_cast(str), - v8::NewStringType::kInternalized, + v8::NewStringType::kNormal, length); CHECK_MAYBE_EMPTY(env, str_maybe, napi_generic_failure); @@ -3587,3 +3954,107 @@ napi_status napi_run_script(napi_env env, *result = v8impl::JsValueFromV8LocalValue(script_result.ToLocalChecked()); return GET_RETURN_STATUS(env); } + +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); + + 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; + } 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_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_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_status +napi_acquire_threadsafe_function(napi_threadsafe_function func) { + CHECK(func != nullptr); + return reinterpret_cast(func)->Acquire(); +} + +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_status +napi_unref_threadsafe_function(napi_env env, napi_threadsafe_function func) { + CHECK(func != nullptr); + return reinterpret_cast(func)->Unref(); +} + +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..f683f8d51b7cd8 100644 --- a/src/node_api.h +++ b/src/node_api.h @@ -3,6 +3,17 @@ #include #include + +#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 4 +#endif +#endif + #include "node_api_types.h" struct uv_loop_s; // Forward declaration. @@ -99,19 +110,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 +426,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,10 +585,76 @@ 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 + +#if NAPI_VERSION >= 4 + +// 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_VERSION >= 4 + EXTERN_C_END #endif // SRC_NODE_API_H_ diff --git a/src/node_api_types.h b/src/node_api_types.h index 76f38802e83e2e..c9229c3bdd3408 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; +#if NAPI_VERSION >= 4 +typedef struct napi_threadsafe_function__* napi_threadsafe_function; +#endif // NAPI_VERSION >= 4 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, +#if NAPI_VERSION >= 4 + napi_queue_full, + napi_closing, +#endif // NAPI_VERSION >= 4 } napi_status; +#if NAPI_VERSION >= 4 +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_VERSION >= 4 + 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); +#if NAPI_VERSION >= 4 +typedef void (*napi_threadsafe_function_call_js)(napi_env env, + napi_value js_callback, + void* context, + void* data); +#endif // NAPI_VERSION >= 4 + typedef struct { // One of utf8name or name should be NULL. const char* utf8name; diff --git a/src/node_crypto.cc b/src/node_crypto.cc index d0e17717d9c2cb..61fefd78b9d970 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -2965,9 +2965,6 @@ void Connection::SetShutdownFlags() { void Connection::NewSessionDoneCb() { - HandleScope scope(env()->isolate()); - - MakeCallback(env()->onnewsessiondone_string(), 0, nullptr); } diff --git a/src/node_http_parser.cc b/src/node_http_parser.cc index 504130e5d7c3f1..1b03fe1c719cad 100644 --- a/src/node_http_parser.cc +++ b/src/node_http_parser.cc @@ -621,7 +621,28 @@ class Parser : public AsyncWrap { size_t nparsed = http_parser_execute(&parser_, &settings, data, len); - Save(); + enum http_errno err = HTTP_PARSER_ERRNO(&parser_); + + // Finish() + if (data == nullptr) { + // `http_parser_execute()` returns either `0` or `1` when `len` is 0 + // (part of the finishing sequence). + CHECK_EQ(len, 0); + switch (nparsed) { + case 0: + err = HPE_OK; + break; + case 1: + nparsed = 0; + break; + default: + UNREACHABLE(); + } + + // Regular Execute() + } else { + Save(); + } // Unassign the 'buffer_' variable current_buffer_.Clear(); @@ -635,7 +656,7 @@ class Parser : public AsyncWrap { Local nparsed_obj = Integer::New(env()->isolate(), nparsed); // If there was a parse error in one of the callbacks // TODO(bnoordhuis) What if there is an error on EOF? - if (!parser_.upgrade && nparsed != len) { + if (!parser_.upgrade && err != HPE_OK) { enum http_errno err = HTTP_PARSER_ERRNO(&parser_); Local e = Exception::Error(env()->parse_error_string()); @@ -646,6 +667,11 @@ class Parser : public AsyncWrap { return scope.Escape(e); } + + // No return value is needed for `Finish()` + if (data == nullptr) { + return scope.Escape(Local()); + } return scope.Escape(nparsed_obj); } diff --git a/src/node_main.cc b/src/node_main.cc index 7ab612d53e500a..8907c47ae0ea4c 100644 --- a/src/node_main.cc +++ b/src/node_main.cc @@ -82,12 +82,30 @@ int wmain(int argc, wchar_t *wargv[]) { #endif // __LP64__ extern char** environ; #endif // __linux__ +#if defined(__POSIX__) && defined(NODE_SHARED_MODE) +#include +#include +#endif namespace node { extern bool linux_at_secure; } // namespace node int main(int argc, char *argv[]) { +#if defined(__POSIX__) && defined(NODE_SHARED_MODE) + // In node::PlatformInit(), we squash all signal handlers for non-shared lib + // build. In order to run test cases against shared lib build, we also need + // to do the same thing for shared lib build here, but only for SIGPIPE for + // now. If node::PlatformInit() is moved to here, then this section could be + // removed. + { + struct sigaction act; + memset(&act, 0, sizeof(act)); + act.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &act, nullptr); + } +#endif + #if defined(__linux__) char** envp = environ; while (*envp++ != nullptr) {} diff --git a/src/node_version.h b/src/node_version.h index 5442c2ef49bc48..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) @@ -108,6 +108,6 @@ #define NODE_MODULE_VERSION 57 // the NAPI_VERSION provided by this version of the runtime -#define NAPI_VERSION 3 +#define NAPI_VERSION 4 #endif // SRC_NODE_VERSION_H_ 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 diff --git a/test/addons-napi/test_general/test.js b/test/addons-napi/test_general/test.js index 4faf508d5db145..03c284bae53869 100644 --- a/test/addons-napi/test_general/test.js +++ b/test/addons-napi/test_general/test.js @@ -33,8 +33,8 @@ assert.notStrictEqual(test_general.testGetPrototype(baseObject), test_general.testGetPrototype(extendedObject)); // test version management functions -// expected version is currently 3 -assert.strictEqual(test_general.testGetVersion(), 3); +// expected version is currently 4 +assert.strictEqual(test_general.testGetVersion(), 4); const [ major, minor, patch, release ] = test_general.testGetNodeVersion(); assert.strictEqual(process.version.split('-')[0], diff --git a/test/addons-napi/test_string/test.js b/test/addons-napi/test_string/test.js index 5ce3d739c7a941..86fdc1640602bc 100644 --- a/test/addons-napi/test_string/test.js +++ b/test/addons-napi/test_string/test.js @@ -73,3 +73,11 @@ assert.strictEqual(test_string.Utf8Length(str6), 14); assert.throws(() => { test_string.TestLargeUtf8(); }, /^Error: Invalid argument$/); + +assert.throws(() => { + test_string.TestLargeLatin1(); +}, /^Error: Invalid argument$/); + +assert.throws(() => { + test_string.TestLargeUtf16(); +}, /^Error: Invalid argument$/); diff --git a/test/addons-napi/test_string/test_string.c b/test/addons-napi/test_string/test_string.c index 99a3f7d3545a20..0f96112970eae1 100644 --- a/test/addons-napi/test_string/test_string.c +++ b/test/addons-napi/test_string/test_string.c @@ -215,6 +215,32 @@ napi_value TestLargeUtf8(napi_env env, napi_callback_info info) { return output; } +static napi_value TestLargeLatin1(napi_env env, napi_callback_info info) { + napi_value output; + if (SIZE_MAX > INT_MAX) { + NAPI_CALL(env, napi_create_string_latin1(env, "", ((size_t)INT_MAX) + 1, &output)); + } else { + // just throw the expected error as there is nothing to test + // in this case since we can't overflow + NAPI_CALL(env, napi_throw_error(env, NULL, "Invalid argument")); + } + + return output; +} + +static napi_value TestLargeUtf16(napi_env env, napi_callback_info info) { + napi_value output; + if (SIZE_MAX > INT_MAX) { + NAPI_CALL(env, napi_create_string_utf16(env, "", ((size_t)INT_MAX) + 1, &output)); + } else { + // just throw the expected error as there is nothing to test + // in this case since we can't overflow + NAPI_CALL(env, napi_throw_error(env, NULL, "Invalid argument")); + } + + return output; +} + napi_value Init(napi_env env, napi_value exports) { napi_property_descriptor properties[] = { DECLARE_NAPI_PROPERTY("TestLatin1", TestLatin1), @@ -226,6 +252,8 @@ napi_value Init(napi_env env, napi_value exports) { DECLARE_NAPI_PROPERTY("Utf16Length", Utf16Length), DECLARE_NAPI_PROPERTY("Utf8Length", Utf8Length), DECLARE_NAPI_PROPERTY("TestLargeUtf8", TestLargeUtf8), + DECLARE_NAPI_PROPERTY("TestLargeLatin1", TestLargeLatin1), + DECLARE_NAPI_PROPERTY("TestLargeUtf16", TestLargeUtf16), }; NAPI_CALL(env, napi_define_properties( 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..d78710cac1317a --- /dev/null +++ b/test/addons-napi/test_threadsafe_function/binding.c @@ -0,0 +1,282 @@ +// 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 +#include +#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; + +typedef struct { + napi_threadsafe_function_call_mode block_on_full; + 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; + +// 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); + 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; + 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 = 4; + napi_value argv[4]; + + 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_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; + 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, 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[] = { + { + "ARRAY_LENGTH", + NULL, + NULL, + NULL, + NULL, + js_array_length, + 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), + 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..ac2549a8c08e5c --- /dev/null +++ b/test/addons-napi/test_threadsafe_function/test.js @@ -0,0 +1,208 @@ +'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 */, +process.argv[3]); + + // 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, + maxQueueSize, + 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, 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. + const start = Date.now(); + while (Date.now() - start < 200); + } + }); +} + +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() { + 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 */, 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)) + +// Start the thread in non-blocking mode, and assert that all values are passed. +// Quit after it's done. +.then(() => testWithJSMarshaller({ + threadStarter: 'StartThreadNonblocking', + maxQueueSize: binding.MAX_QUEUE_SIZE, + 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', + 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)) + +// 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', + maxQueueSize: binding.MAX_QUEUE_SIZE, + 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, + maxQueueSize: binding.MAX_QUEUE_SIZE, + 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, + 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 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)) + +// Start the thread in non-blocking mode, and assert that it could not finish. +// Quit early and aborting. +.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(() => testUnref(binding.MAX_QUEUE_SIZE)) + +// Start a child process with an infinite queue to test rapid teardown +.then(() => testUnref(0)); 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 diff --git a/test/parallel/test-fs-utimes.js b/test/parallel/test-fs-utimes.js index 3dc0bb59def6a2..ad83bc93653256 100644 --- a/test/parallel/test-fs-utimes.js +++ b/test/parallel/test-fs-utimes.js @@ -35,7 +35,11 @@ function stat_resource(resource) { if (typeof resource === 'string') { return fs.statSync(resource); } else { + const stats = fs.fstatSync(resource); // ensure mtime has been written to disk + // except for directories on AIX where it cannot be synced + if (common.isAIX && stats.isDirectory()) + return stats; fs.fsyncSync(resource); return fs.fstatSync(resource); } diff --git a/test/parallel/test-http-max-header-size.js b/test/parallel/test-http-max-header-size.js index 07fbe902963525..43dcbec5ab8967 100644 --- a/test/parallel/test-http-max-header-size.js +++ b/test/parallel/test-http-max-header-size.js @@ -1,6 +1,6 @@ 'use strict'; -require('../common'); +const common = require('../common'); const assert = require('assert'); const { spawnSync } = require('child_process'); const http = require('http'); @@ -9,3 +9,16 @@ assert.strictEqual(http.maxHeaderSize, 8 * 1024); const child = spawnSync(process.execPath, ['--max-http-header-size=10', '-p', 'http.maxHeaderSize']); assert.strictEqual(+child.stdout.toString().trim(), 10); + +{ + const server = http.createServer(common.mustNotCall()); + server.listen(0, common.mustCall(() => { + http.get({ + port: server.address().port, + headers: { foo: 'x'.repeat(http.maxHeaderSize + 1) } + }, common.mustNotCall()).once('error', common.mustCall((err) => { + assert.strictEqual(err.code, 'ECONNRESET'); + server.close(); + })); + })); +} diff --git a/test/parallel/test-http-parser-reuse-domain.js b/test/parallel/test-http-parser-reuse-domain.js new file mode 100644 index 00000000000000..fd164f47d1e007 --- /dev/null +++ b/test/parallel/test-http-parser-reuse-domain.js @@ -0,0 +1,112 @@ +'use strict'; + +const common = require('../common'); +const domain = require('domain'); +const http = require('http'); + +const agent = new http.Agent({ + // keepAlive is important here so that the underlying socket of all requests + // is reused and tied to the same domain. + keepAlive: true +}); + +const reqSockets = new Set(); + +function performHttpRequest(opts, cb) { + const req = http.get({ port: server.address().port, agent }, (res) => { + if (!req.socket) { + console.log('missing socket property on req object, failing test'); + markTestAsFailed(); + req.abort(); + cb(new Error('req.socket missing')); + return; + } + + reqSockets.add(req.socket); + + // Consume the data from the response to make sure the 'end' event is + // emitted. + res.on('data', function noop() {}); + res.on('end', () => { + if (opts.shouldThrow) { + throw new Error('boom'); + } else { + cb(); + } + }); + + res.on('error', (resErr) => { + console.log('got error on response, failing test:', resErr); + markTestAsFailed(); + }); + + }).on('error', (reqErr) => { + console.log('got error on request, failing test:', reqErr); + markTestAsFailed(); + }); + + req.end(); +} + +function performHttpRequestInNewDomain(opts, cb) { + const d = domain.create(); + d.run(() => { + performHttpRequest(opts, cb); + }); + + return d; +} + +const server = http.createServer((req, res) => { + res.end(); +}); + +server.listen(0, common.mustCall(function() { + const d1 = performHttpRequestInNewDomain({ + shouldThrow: false + }, common.mustCall((firstReqErr) => { + if (firstReqErr) { + markTestAsFailed(); + return; + } + // We want to schedule the second request on the next turn of the event + // loop so that the parser from the first request is actually reused. + setImmediate(common.mustCall(() => { + const d2 = performHttpRequestInNewDomain({ + shouldThrow: true + }, (secondReqErr) => { + // Since this request throws before its callback has the chance + // to be called, we mark the test as failed if this callback is + // called. + console.log('second request\'s cb called unexpectedly, failing test'); + markTestAsFailed(); + }); + + // The second request throws when its response's end event is + // emitted. So we expect its domain to emit an error event. + d2.on('error', common.mustCall((d2err) => { + server.close(); + if (reqSockets.size !== 1) { + console.log('more than 1 socket used for both requests, failing ' + + 'test'); + markTestAsFailed(); + } + }, 1)); + })); + })); + + d1.on('error', (d1err) => { + // d1 is the domain attached to the first request, which doesn't throw, + // so we don't expect its error handler to be called. + console.log('first request\'s domain\'s error handler called, ' + + 'failing test'); + markTestAsFailed(); + }); +})); + +function markTestAsFailed() { + if (server) { + server.close(); + } + process.exitCode = 1; +} 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] || ['']; 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, 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/parallel/test-tls-async-cb-after-socket-end-securepair.js b/test/parallel/test-tls-async-cb-after-socket-end-securepair.js new file mode 100644 index 00000000000000..890c2b63ef5c21 --- /dev/null +++ b/test/parallel/test-tls-async-cb-after-socket-end-securepair.js @@ -0,0 +1,80 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); +const fixtures = require('../common/fixtures'); +const SSL_OP_NO_TICKET = require('crypto').constants.SSL_OP_NO_TICKET; +const assert = require('assert'); +const net = require('net'); +const tls = require('tls'); + +const options = { + secureOptions: SSL_OP_NO_TICKET, + key: fixtures.readSync('test_key.pem'), + cert: fixtures.readSync('test_cert.pem') +}; + +const server = net.createServer(function(socket) { + const sslcontext = tls.createSecureContext(options); + sslcontext.context.setCiphers('RC4-SHA:AES128-SHA:AES256-SHA'); + + const pair = tls.createSecurePair(sslcontext, true, false, false, { server }); + + assert.ok(pair.encrypted.writable); + assert.ok(pair.cleartext.writable); + + pair.encrypted.pipe(socket); + socket.pipe(pair.encrypted); + + pair.on('error', () => {}); // Expected, client s1 closes connection. +}); + +let sessionCb = null; +let client = null; + +server.on('newSession', common.mustCall(function(key, session, done) { + done(); +})); + +server.on('resumeSession', common.mustCall(function(id, cb) { + sessionCb = cb; + + next(); +})); + +server.listen(0, function() { + const clientOpts = { + port: this.address().port, + rejectUnauthorized: false, + session: false + }; + + const s1 = tls.connect(clientOpts, function() { + clientOpts.session = s1.getSession(); + console.log('1st secure'); + + s1.destroy(); + const s2 = tls.connect(clientOpts, function(s) { + console.log('2nd secure'); + + s2.destroy(); + }).on('connect', function() { + console.log('2nd connected'); + client = s2; + + next(); + }); + }); +}); + +function next() { + if (!client || !sessionCb) + return; + + client.destroy(); + setTimeout(function() { + sessionCb(); + server.close(); + }, 100); +} 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 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 @@ + 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 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/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'); } 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() diff --git a/vcbuild.bat b/vcbuild.bat index 43962324857bed..7143d841a4e2bc 100644 --- a/vcbuild.bat +++ b/vcbuild.bat @@ -474,6 +474,7 @@ if errorlevel 1 goto exit if "%test_args%"=="" goto test-v8 if "%config%"=="Debug" set test_args=--mode=debug %test_args% if "%config%"=="Release" set test_args=--mode=release %test_args% +if not exist %config%\cctest.exe goto run-test-py echo running 'cctest %cctest_args%' "%config%\cctest" %cctest_args% REM when building a static library there's no binary to run tests