diff --git a/CHANGELOG.md b/CHANGELOG.md
index bddf738e117443..f7d1c7d3e2007e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -33,7 +33,8 @@ release.
12.0.0
in header elements (Rich Trott) [#31086](https://github.com/nodejs/node/pull/31086)
+* [[`a1b095dd46`](https://github.com/nodejs/node/commit/a1b095dd46)] - **doc,dns**: use code markup/markdown in headers (Rich Trott) [#31086](https://github.com/nodejs/node/pull/31086)
+* [[`8f3b8ca515`](https://github.com/nodejs/node/commit/8f3b8ca515)] - **http2**: fix session memory accounting after pausing (Michael Lehenbauer) [#30684](https://github.com/nodejs/node/pull/30684)
+* [[`20f64a96de`](https://github.com/nodejs/node/commit/20f64a96de)] - **http2**: use the latest settings (ZYSzys) [#29780](https://github.com/nodejs/node/pull/29780)
+* [[`81c31005fd`](https://github.com/nodejs/node/commit/81c31005fd)] - **lib**: fix comment nits in bootstrap\loaders.js (Vse Mozhet Byt) [#24641](https://github.com/nodejs/node/pull/24641)
+* [[`88e8b7cf83`](https://github.com/nodejs/node/commit/88e8b7cf83)] - **n-api**: correct bug in napi_get_last_error (Octavian Soldea) [#28702](https://github.com/nodejs/node/pull/28702)
+* [[`77e0318849`](https://github.com/nodejs/node/commit/77e0318849)] - **stream**: increase MAX_HWM (Robert Nagy) [#29938](https://github.com/nodejs/node/pull/29938)
+* [[`894aaa2040`](https://github.com/nodejs/node/commit/894aaa2040)] - **stream**: extract Readable.from in its own file (Matteo Collina) [#30140](https://github.com/nodejs/node/pull/30140)
+* [[`7e941eb17d`](https://github.com/nodejs/node/commit/7e941eb17d)] - **test**: do not fail SLOW tests if they are not slow (Yang Guo) [#25868](https://github.com/nodejs/node/pull/25868)
+* [[`0f3ae77aaf`](https://github.com/nodejs/node/commit/0f3ae77aaf)] - **tools**: update tzdata to 2019c (Myles Borins) [#30479](https://github.com/nodejs/node/pull/30479)
+* [[`4ae8d204cb`](https://github.com/nodejs/node/commit/4ae8d204cb)] - **tools**: move python code out of jenkins shell (Sam Roberts) [#28458](https://github.com/nodejs/node/pull/28458)
+* [[`4879b80d87`](https://github.com/nodejs/node/commit/4879b80d87)] - **tools**: fix v8 testing with devtoolset on ppcle (Sam Roberts) [#28458](https://github.com/nodejs/node/pull/28458)
+
## 2019-12-17, Version 10.18.0 'Dubnium' (LTS), @MylesBorins
diff --git a/lib/_stream_readable.js b/lib/_stream_readable.js
index 88193cd3b76219..98216b411f5862 100644
--- a/lib/_stream_readable.js
+++ b/lib/_stream_readable.js
@@ -42,6 +42,7 @@ const {
// Lazy loaded to improve the startup performance.
let StringDecoder;
let createReadableStreamAsyncIterator;
+let from;
util.inherits(Readable, Stream);
@@ -339,10 +340,11 @@ Readable.prototype.setEncoding = function(enc) {
return this;
};
-// Don't raise the hwm > 8MB
-const MAX_HWM = 0x800000;
+// Don't raise the hwm > 1GB
+const MAX_HWM = 0x40000000;
function computeNewHighWaterMark(n) {
if (n >= MAX_HWM) {
+ // TODO(ronag): Throw ERR_VALUE_OUT_OF_RANGE.
n = MAX_HWM;
} else {
// Get the next highest power of 2 to prevent increasing hwm excessively in
@@ -1154,40 +1156,8 @@ function endReadableNT(state, stream) {
}
Readable.from = function(iterable, opts) {
- let iterator;
- if (iterable && iterable[Symbol.asyncIterator])
- iterator = iterable[Symbol.asyncIterator]();
- else if (iterable && iterable[Symbol.iterator])
- iterator = iterable[Symbol.iterator]();
- else
- throw new ERR_INVALID_ARG_TYPE('iterable', ['Iterable'], iterable);
-
- const readable = new Readable({
- objectMode: true,
- ...opts
- });
- // Reading boolean to protect against _read
- // being called before last iteration completion.
- let reading = false;
- readable._read = function() {
- if (!reading) {
- reading = true;
- next();
- }
- };
- async function next() {
- try {
- const { value, done } = await iterator.next();
- if (done) {
- readable.push(null);
- } else if (readable.push(await value)) {
- next();
- } else {
- reading = false;
- }
- } catch (err) {
- readable.destroy(err);
- }
+ if (from === undefined) {
+ from = require('internal/streams/from');
}
- return readable;
+ return from(Readable, iterable, opts);
};
diff --git a/lib/internal/bootstrap/loaders.js b/lib/internal/bootstrap/loaders.js
index 06aee591496440..89087320ed0913 100644
--- a/lib/internal/bootstrap/loaders.js
+++ b/lib/internal/bootstrap/loaders.js
@@ -19,7 +19,7 @@
// can be created using NODE_MODULE_CONTEXT_AWARE_CPP() with the flag
// NM_F_LINKED.
// - internalBinding(): the private internal C++ binding loader, inaccessible
-// from user land because they are only available from NativeModule.require()
+// from user land because they are only available from NativeModule.require().
// These C++ bindings are created using NODE_MODULE_CONTEXT_AWARE_INTERNAL()
// and have their nm_flags set to NM_F_INTERNAL.
//
@@ -61,7 +61,7 @@
keys: ObjectKeys,
} = Object;
- // Set up process.moduleLoadList
+ // Set up process.moduleLoadList.
const moduleLoadList = [];
ObjectDefineProperty(process, 'moduleLoadList', {
value: moduleLoadList,
@@ -70,7 +70,7 @@
writable: false
});
- // Set up process.binding() and process._linkedBinding()
+ // Set up process.binding() and process._linkedBinding().
{
const bindingObj = ObjectCreate(null);
@@ -93,7 +93,7 @@
};
}
- // Set up internalBinding() in the closure
+ // Set up internalBinding() in the closure.
let internalBinding;
{
const bindingObj = ObjectCreate(null);
@@ -115,11 +115,11 @@
};
}
- // Create this WeakMap in js-land because V8 has no C++ API for WeakMap
+ // Create this WeakMap in js-land because V8 has no C++ API for WeakMap.
internalBinding('module_wrap').callbackMap = new WeakMap();
const { ContextifyScript } = process.binding('contextify');
- // Set up NativeModule
+ // Set up NativeModule.
function NativeModule(id) {
this.filename = `${id}.js`;
this.id = id;
@@ -128,7 +128,7 @@
this.exportKeys = undefined;
this.loaded = false;
this.loading = false;
- this.script = null; // The ContextifyScript of the module
+ this.script = null; // The ContextifyScript of the module.
}
NativeModule._source = getBinding('natives');
@@ -160,7 +160,7 @@
if (!NativeModule.exists(id)) {
// Model the error off the internal/errors.js model, but
// do not use that module given that it could actually be
- // the one causing the error if there's a bug in Node.js
+ // the one causing the error if there's a bug in Node.js.
// eslint-disable-next-line no-restricted-syntax
const err = new Error(`No such built-in module: ${id}`);
err.code = 'ERR_UNKNOWN_BUILTIN_MODULE';
@@ -201,7 +201,7 @@
if (config.exposeInternals) {
NativeModule.nonInternalExists = function(id) {
- // Do not expose this to user land even with --expose-internals
+ // Do not expose this to user land even with --expose-internals.
if (id === loaderId) {
return false;
}
@@ -209,7 +209,7 @@
};
NativeModule.isInternal = function(id) {
- // Do not expose this to user land even with --expose-internals
+ // Do not expose this to user land even with --expose-internals.
return id === loaderId;
};
} else {
@@ -243,7 +243,7 @@
};
// Provide named exports for all builtin libraries so that the libraries
- // may be imported in a nicer way for esm users. The default export is left
+ // may be imported in a nicer way for ESM users. The default export is left
// as the entire namespace (module.exports) and wrapped in a proxy such
// that APMs and other behavior are still left intact.
NativeModule.prototype.proxifyExports = function() {
diff --git a/lib/internal/http2/core.js b/lib/internal/http2/core.js
index 5046539c570841..bb9e43ca8ea0bc 100644
--- a/lib/internal/http2/core.js
+++ b/lib/internal/http2/core.js
@@ -504,6 +504,7 @@ function onSettings() {
return;
session[kUpdateTimer]();
debugSessionObj(session, 'new settings received');
+ session[kRemoteSettings] = undefined;
session.emit('remoteSettings', session.remoteSettings);
}
diff --git a/lib/internal/streams/from.js b/lib/internal/streams/from.js
new file mode 100644
index 00000000000000..e809f2658d5a9a
--- /dev/null
+++ b/lib/internal/streams/from.js
@@ -0,0 +1,46 @@
+'use strict';
+
+const {
+ ERR_INVALID_ARG_TYPE
+} = require('internal/errors').codes;
+
+function from(Readable, iterable, opts) {
+ let iterator;
+ if (iterable && iterable[Symbol.asyncIterator])
+ iterator = iterable[Symbol.asyncIterator]();
+ else if (iterable && iterable[Symbol.iterator])
+ iterator = iterable[Symbol.iterator]();
+ else
+ throw new ERR_INVALID_ARG_TYPE('iterable', ['Iterable'], iterable);
+
+ const readable = new Readable({
+ objectMode: true,
+ ...opts
+ });
+ // Reading boolean to protect against _read
+ // being called before last iteration completion.
+ let reading = false;
+ readable._read = function() {
+ if (!reading) {
+ reading = true;
+ next();
+ }
+ };
+ async function next() {
+ try {
+ const { value, done } = await iterator.next();
+ if (done) {
+ readable.push(null);
+ } else if (readable.push(await value)) {
+ next();
+ } else {
+ reading = false;
+ }
+ } catch (err) {
+ readable.destroy(err);
+ }
+ }
+ return readable;
+}
+
+module.exports = from;
diff --git a/node.gyp b/node.gyp
index e50a284b3011f0..dd60ea4a937691 100644
--- a/node.gyp
+++ b/node.gyp
@@ -178,6 +178,7 @@
'lib/internal/streams/async_iterator.js',
'lib/internal/streams/buffer_list.js',
'lib/internal/streams/duplexpair.js',
+ 'lib/internal/streams/from.js',
'lib/internal/streams/legacy.js',
'lib/internal/streams/destroy.js',
'lib/internal/streams/state.js',
diff --git a/src/node_api.cc b/src/node_api.cc
index f04a5bbfb930a9..39b4fe8bca595f 100644
--- a/src/node_api.cc
+++ b/src/node_api.cc
@@ -1407,14 +1407,16 @@ napi_status napi_get_last_error_info(napi_env env,
CHECK_ENV(env);
CHECK_ARG(env, result);
- // you must update this assert to reference the last message
- // in the napi_status enum each time a new error message is added.
+ // The value of the constant below must be updated to reference the last
+ // message in the `napi_status` enum each time a new error message is added.
// We don't have a napi_status_last as this would result in an ABI
// change each time a message was added.
+ const int last_status = napi_date_expected;
+
static_assert(
- node::arraysize(error_messages) == napi_date_expected + 1,
+ node::arraysize(error_messages) == last_status + 1,
"Count of error messages must match count of error values");
- CHECK_LE(env->last_error.error_code, napi_callback_scope_mismatch);
+ CHECK_LE(env->last_error.error_code, last_status);
// Wait until someone requests the last error information to fetch the error
// message string
diff --git a/src/node_api_types.h b/src/node_api_types.h
index 0aece04aeef85b..211e4611c2d70b 100644
--- a/src/node_api_types.h
+++ b/src/node_api_types.h
@@ -84,6 +84,10 @@ typedef enum {
napi_bigint_expected,
napi_date_expected,
} napi_status;
+// Note: when adding a new enum value to `napi_status`, please also update
+// `const int last_status` in `napi_get_last_error_info()' definition,
+// in file js_native_api_v8.cc. Please also update the definition of
+// `napi_status` in doc/api/n-api.md to reflect the newly added value(s).
#if NAPI_VERSION >= 4
typedef enum {
diff --git a/src/node_http2.cc b/src/node_http2.cc
index 21051e1721965b..35e0e8f623e3e6 100644
--- a/src/node_http2.cc
+++ b/src/node_http2.cc
@@ -1937,7 +1937,10 @@ void Http2Session::OnStreamRead(ssize_t nread, const uv_buf_t& buf_) {
buf = uv_buf_init(new_buf, nread);
stream_buf_offset_ = 0;
stream_buf_ab_.Reset();
- DecrementCurrentSessionMemory(stream_buf_offset_);
+
+ // We have now fully processed the stream_buf_ input chunk (by moving the
+ // remaining part into buf, which will be accounted for below).
+ DecrementCurrentSessionMemory(stream_buf_.len);
}
// Shrink to the actual amount of used data.
diff --git a/src/node_version.h b/src/node_version.h
index 37671594fcaed2..13151531830c7b 100644
--- a/src/node_version.h
+++ b/src/node_version.h
@@ -29,7 +29,7 @@
#define NODE_VERSION_IS_LTS 1
#define NODE_VERSION_LTS_CODENAME "Dubnium"
-#define NODE_VERSION_IS_RELEASE 0
+#define NODE_VERSION_IS_RELEASE 1
#ifndef NODE_STRINGIFY
#define NODE_STRINGIFY(n) NODE_STRINGIFY_HELPER(n)
diff --git a/test/parallel/test-http2-large-writes-session-memory-leak.js b/test/parallel/test-http2-large-writes-session-memory-leak.js
new file mode 100644
index 00000000000000..641923c06c9133
--- /dev/null
+++ b/test/parallel/test-http2-large-writes-session-memory-leak.js
@@ -0,0 +1,55 @@
+'use strict';
+const common = require('../common');
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+const fixtures = require('../common/fixtures');
+const http2 = require('http2');
+
+// Regression test for https://github.com/nodejs/node/issues/29223.
+// There was a "leak" in the accounting of session memory leading
+// to streams eventually failing with NGHTTP2_ENHANCE_YOUR_CALM.
+
+const server = http2.createSecureServer({
+ key: fixtures.readKey('agent2-key.pem'),
+ cert: fixtures.readKey('agent2-cert.pem'),
+});
+
+// Simple server that sends 200k and closes the stream.
+const data200k = 'a'.repeat(200 * 1024);
+server.on('stream', (stream) => {
+ stream.write(data200k);
+ stream.end();
+});
+
+server.listen(0, common.mustCall(() => {
+ const client = http2.connect(`https://localhost:${server.address().port}`, {
+ ca: fixtures.readKey('agent2-cert.pem'),
+ servername: 'agent2',
+
+ // Set maxSessionMemory to 1MB so the leak causes errors faster.
+ maxSessionMemory: 1
+ });
+
+ // Repeatedly create a new stream and read the incoming data. Even though we
+ // only have one stream active at a time, prior to the fix for #29223,
+ // session memory would steadily increase and we'd eventually hit the 1MB
+ // maxSessionMemory limit and get NGHTTP2_ENHANCE_YOUR_CALM errors trying to
+ // create new streams.
+ let streamsLeft = 50;
+ function newStream() {
+ const stream = client.request({ ':path': '/' });
+
+ stream.on('data', () => { });
+
+ stream.on('close', () => {
+ if (streamsLeft-- > 0) {
+ newStream();
+ } else {
+ client.destroy();
+ server.close();
+ }
+ });
+ }
+
+ newStream();
+}));
diff --git a/test/parallel/test-http2-session-settings.js b/test/parallel/test-http2-session-settings.js
index 6061808082519d..29b6365ef1a3c4 100644
--- a/test/parallel/test-http2-session-settings.js
+++ b/test/parallel/test-http2-session-settings.js
@@ -38,6 +38,12 @@ server.on(
})
);
+server.on('session', (session) => {
+ session.settings({
+ maxConcurrentStreams: 2
+ });
+});
+
server.listen(
0,
common.mustCall(() => {
@@ -57,11 +63,18 @@ server.listen(
assert.strictEqual(settings.maxFrameSize, 16384);
}, 2)
);
+
+ let calledOnce = false;
client.on(
'remoteSettings',
common.mustCall((settings) => {
assert(settings);
- })
+ assert.strictEqual(
+ settings.maxConcurrentStreams,
+ calledOnce ? 2 : (2 ** 32) - 1
+ );
+ calledOnce = true;
+ }, 2)
);
const headers = { ':path': '/' };
diff --git a/test/parallel/test-readable-large-hwm.js b/test/parallel/test-readable-large-hwm.js
new file mode 100644
index 00000000000000..d5bf25bc0e61c1
--- /dev/null
+++ b/test/parallel/test-readable-large-hwm.js
@@ -0,0 +1,27 @@
+'use strict';
+const common = require('../common');
+const { Readable } = require('stream');
+
+// Make sure that readable completes
+// even when reading larger buffer.
+const bufferSize = 10 * 1024 * 1024;
+let n = 0;
+const r = new Readable({
+ read() {
+ // Try to fill readable buffer piece by piece.
+ r.push(Buffer.alloc(bufferSize / 10));
+
+ if (n++ > 10) {
+ r.push(null);
+ }
+ }
+});
+
+r.on('readable', () => {
+ while (true) {
+ const ret = r.read(bufferSize);
+ if (ret === null)
+ break;
+ }
+});
+r.on('end', common.mustCall());
diff --git a/tools/getarch.py b/tools/getarch.py
new file mode 100644
index 00000000000000..3c366525463340
--- /dev/null
+++ b/tools/getarch.py
@@ -0,0 +1,10 @@
+from __future__ import print_function
+from utils import GuessArchitecture
+arch = GuessArchitecture()
+
+# assume 64 bit unless set specifically
+print(GuessArchitecture() \
+ .replace('ia32', 'x64') \
+ .replace('ppc', 'ppc64') \
+ .replace('arm', 'arm64') \
+ .replace('s390', 's390x'))
diff --git a/tools/getendian.py b/tools/getendian.py
new file mode 100644
index 00000000000000..0f9fcc1c860584
--- /dev/null
+++ b/tools/getendian.py
@@ -0,0 +1,4 @@
+from __future__ import print_function
+import sys
+# "little" or "big"
+print(sys.byteorder)
diff --git a/tools/getmachine.py b/tools/getmachine.py
new file mode 100644
index 00000000000000..046d8b17a797fd
--- /dev/null
+++ b/tools/getmachine.py
@@ -0,0 +1,3 @@
+from __future__ import print_function
+import platform
+print(platform.machine())
diff --git a/tools/getnodeversion.py b/tools/getnodeversion.py
index 59f8aabe49eceb..c9f82160c0f386 100644
--- a/tools/getnodeversion.py
+++ b/tools/getnodeversion.py
@@ -1,3 +1,4 @@
+from __future__ import print_function
import os
import re
diff --git a/tools/make-v8.sh b/tools/make-v8.sh
index fd66fda94274df..1a6e175b6158cc 100755
--- a/tools/make-v8.sh
+++ b/tools/make-v8.sh
@@ -12,7 +12,9 @@ if [[ "$ARCH" == "s390x" ]] || [[ "$ARCH" == "ppc64le" ]]; then
export BUILD_TOOLS=/home/iojs/build-tools
export LD_LIBRARY_PATH=$BUILD_TOOLS:$LD_LIBRARY_PATH
export PATH=$BUILD_TOOLS:$PATH
- CXX_PATH=`which $CXX |grep g++`
+ if [[ X"$CXX" != X ]]; then
+ CXX_PATH=`which $CXX |grep g++`
+ fi
rm -f "$BUILD_TOOLS/g++"
rm -f "$BUILD_TOOLS/gcc"
fi
@@ -24,8 +26,10 @@ if [[ "$ARCH" == "s390x" ]]; then
gn gen -v out.gn/$BUILD_ARCH_TYPE --args='is_component_build=false is_debug=false use_goma=false goma_dir="None" use_custom_libcxx=false v8_target_cpu="s390x" target_cpu="s390x"'
ninja -v -C out.gn/$BUILD_ARCH_TYPE d8 cctest inspector-test
elif [[ "$ARCH" == "ppc64le" ]]; then
- ln -s /usr/bin/$CXX "$BUILD_TOOLS/g++"
- ln -s /usr/bin/$CC "$BUILD_TOOLS/gcc"
+ if [[ X"$CXX" != X ]]; then
+ ln -s /usr/bin/$CXX "$BUILD_TOOLS/g++"
+ ln -s /usr/bin/$CC "$BUILD_TOOLS/gcc"
+ fi
g++ --version
export PKG_CONFIG_PATH=$BUILD_TOOLS/pkg-config-files
gn gen out.gn/$BUILD_ARCH_TYPE --args='is_component_build=false is_debug=false use_goma=false goma_dir="None" use_custom_libcxx=false v8_target_cpu="ppc64" target_cpu="ppc64"'
diff --git a/tools/test.py b/tools/test.py
index 3ddbb64a91e3d7..1f0a037b47c6e8 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -1184,6 +1184,9 @@ def ClassifyTests(self, cases, env):
outcomes = reduce(set.union, outcomes_list, set())
unused_rules.difference_update(matches)
case.outcomes = set(outcomes) or set([PASS])
+ # slow tests may also just pass.
+ if SLOW in case.outcomes:
+ case.outcomes.add(PASS)
result.append(case)
return result, unused_rules
diff --git a/vcbuild.bat b/vcbuild.bat
index 26c6b26dee69c0..c7c156e9b41d2d 100644
--- a/vcbuild.bat
+++ b/vcbuild.bat
@@ -68,7 +68,8 @@ set no_cctest=
set cctest=
set openssl_no_asm=
set doc=
-set extra_msbuild_args=^
+set extra_msbuild_args=
+set exit_code=0
:next-arg
if "%1"=="" goto args-done
@@ -559,9 +560,11 @@ if defined no_cctest echo Skipping cctest because no-cctest was specified && got
if not exist "%config%\cctest.exe" echo cctest.exe not found. Run "vcbuild test" or "vcbuild cctest" to build it. && goto run-test-py
echo running 'cctest %cctest_args%'
"%config%\cctest" %cctest_args%
+if %errorlevel% neq 0 set exit_code=%errorlevel%
:run-test-py
echo running 'python tools\test.py %test_args%'
python tools\test.py %test_args%
+if %errorlevel% neq 0 set exit_code=%errorlevel%
goto test-v8
:test-v8
@@ -672,7 +675,7 @@ echo vcbuild.bat no-cctest : skip building cctest.exe
goto exit
:exit
-goto :EOF
+exit /b %exit_code%
rem ***************