Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/api/http2.md
Original file line number Diff line number Diff line change
Expand Up @@ -3650,7 +3650,7 @@ properties.
permitted on the `Http2Session` instances. **Default:** `true`.
* `initialWindowSize` {number} Specifies the _sender's_ initial window size in
bytes for stream-level flow control. The minimum allowed value is 0. The
maximum allowed value is 2<sup>32</sup>-1. **Default:** `65535`.
maximum allowed value is 2<sup>32</sup>-1. **Default:** `4194304`.
* `maxFrameSize` {number} Specifies the size in bytes of the largest frame
payload. The minimum allowed value is 16,384. The maximum allowed value is
2<sup>24</sup>-1. **Default:** `16384`.
Expand Down
11 changes: 10 additions & 1 deletion lib/internal/http2/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -1205,7 +1205,16 @@ function setupHandle(socket, type, options) {
}

const settings = typeof options.settings === 'object' ?
options.settings : {};
{ ...options.settings } : {};

// Increase the default initial window size to improve throughput
// on high-latency connections. The HTTP/2 default of 65535 (64KB)
// limits throughput to window_size / RTT. By increasing to 4MB,
// throughput is significantly improved.
// See https://github.com/nodejs/node/issues/38426
if (settings.initialWindowSize === undefined) {
settings.initialWindowSize = constants.DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE;
}

this.settings(settings);

Expand Down
11 changes: 11 additions & 0 deletions src/node_http2.cc
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,17 @@ Http2Session::Http2Session(Http2State* http2_state,
&alloc_info), 0);
session_.reset(session);

// Increase the default local connection window to improve throughput
// on high-latency connections. The default 64KB window limits throughput
// to window_size / RTT. With a 32MB connection window, throughput is
// significantly improved. See https://github.com/nodejs/node/issues/38426
CHECK_EQ(nghttp2_session_set_local_window_size(
session,
NGHTTP2_FLAG_NONE,
0,
DEFAULT_SETTINGS_LOCAL_CONNECTION_WINDOW_SIZE),
0);

outgoing_storage_.reserve(1024);
outgoing_buffers_.reserve(32);

Expand Down
6 changes: 5 additions & 1 deletion src/node_http2.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,18 @@ constexpr uint64_t kDefaultMaxSessionMemory = 10000000;
constexpr uint32_t DEFAULT_SETTINGS_HEADER_TABLE_SIZE = 4096;
constexpr uint32_t DEFAULT_SETTINGS_ENABLE_PUSH = 1;
constexpr uint32_t DEFAULT_SETTINGS_MAX_CONCURRENT_STREAMS = 0xffffffffu;
constexpr uint32_t DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE = 65535;
constexpr uint32_t DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE = 4194304;
constexpr uint32_t DEFAULT_SETTINGS_MAX_FRAME_SIZE = 16384;
constexpr uint32_t DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE = 65535;
constexpr uint32_t DEFAULT_SETTINGS_ENABLE_CONNECT_PROTOCOL = 0;
constexpr uint32_t MAX_MAX_FRAME_SIZE = 16777215;
constexpr uint32_t MIN_MAX_FRAME_SIZE = DEFAULT_SETTINGS_MAX_FRAME_SIZE;
constexpr uint32_t MAX_INITIAL_WINDOW_SIZE = 2147483647;

// Default local connection window size (32MB) to improve throughput
// on high-latency connections. See https://github.com/nodejs/node/issues/38426
constexpr uint32_t DEFAULT_SETTINGS_LOCAL_CONNECTION_WINDOW_SIZE = 33554432;

// Stream is not going to have any DATA frames
constexpr int STREAM_OPTION_EMPTY_PAYLOAD = 0x1;

Expand Down
4 changes: 2 additions & 2 deletions test/parallel/test-http2-binding.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const settings = http2.getDefaultSettings();
assert.strictEqual(settings.headerTableSize, 4096);
assert.strictEqual(settings.enablePush, true);
assert.strictEqual(settings.maxConcurrentStreams, 4294967295);
assert.strictEqual(settings.initialWindowSize, 65535);
assert.strictEqual(settings.initialWindowSize, 4194304);
assert.strictEqual(settings.maxFrameSize, 16384);

assert.strictEqual(binding.nghttp2ErrorString(-517),
Expand Down Expand Up @@ -239,7 +239,7 @@ const defaultSettings = {
DEFAULT_SETTINGS_HEADER_TABLE_SIZE: 4096,
DEFAULT_SETTINGS_ENABLE_PUSH: 1,
DEFAULT_SETTINGS_MAX_CONCURRENT_STREAMS: 4294967295,
DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE: 65535,
DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE: 4194304,
DEFAULT_SETTINGS_MAX_FRAME_SIZE: 16384,
DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE: 65535,
DEFAULT_SETTINGS_ENABLE_CONNECT_PROTOCOL: 0
Expand Down
29 changes: 14 additions & 15 deletions test/parallel/test-http2-client-setLocalWindowSize.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,16 @@ const http2 = require('http2');

client.on('connect', common.mustCall(() => {
const windowSize = 2 ** 20;
const defaultSetting = http2.getDefaultSettings();
client.setLocalWindowSize(windowSize);

assert.strictEqual(client.state.effectiveLocalWindowSize, windowSize);
assert.strictEqual(client.state.localWindowSize, windowSize);
assert.strictEqual(
client.state.remoteWindowSize,
defaultSetting.initialWindowSize
);
// localWindowSize returns the available connection window.
// When decreasing from the default 33554432 to 1048576,
// the available window stays at 33554432.
assert.strictEqual(client.state.localWindowSize, 33554432);
// remoteWindowSize is the connection-level send window,
// which remains at the HTTP/2 default of 65535.
assert.strictEqual(client.state.remoteWindowSize, 65535);

server.close();
client.close();
Expand All @@ -101,18 +102,16 @@ const http2 = require('http2');

client.on('connect', common.mustCall(() => {
const windowSize = 20;
const defaultSetting = http2.getDefaultSettings();
client.setLocalWindowSize(windowSize);

assert.strictEqual(client.state.effectiveLocalWindowSize, windowSize);
assert.strictEqual(
client.state.localWindowSize,
defaultSetting.initialWindowSize
);
assert.strictEqual(
client.state.remoteWindowSize,
defaultSetting.initialWindowSize
);
// localWindowSize returns the available connection window.
// When decreasing from the default 33554432 to 20,
// the available window stays at 33554432.
assert.strictEqual(client.state.localWindowSize, 33554432);
// remoteWindowSize is the connection-level send window,
// which remains at the HTTP/2 default of 65535.
assert.strictEqual(client.state.remoteWindowSize, 65535);

server.close();
client.close();
Expand Down
2 changes: 1 addition & 1 deletion test/parallel/test-http2-getpackedsettings.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const http2 = require('http2');
const check = Buffer.from([0x00, 0x01, 0x00, 0x00, 0x10, 0x00,
0x00, 0x02, 0x00, 0x00, 0x00, 0x01,
0x00, 0x03, 0xff, 0xff, 0xff, 0xff,
0x00, 0x04, 0x00, 0x00, 0xff, 0xff,
0x00, 0x04, 0x00, 0x40, 0x00, 0x00,
0x00, 0x05, 0x00, 0x00, 0x40, 0x00,
0x00, 0x06, 0x00, 0x00, 0xff, 0xff,
0x00, 0x08, 0x00, 0x00, 0x00, 0x00]);
Expand Down
6 changes: 4 additions & 2 deletions test/parallel/test-http2-pack-end-stream-flag.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,10 @@ function testRequest(path, targetFrameCount, callback) {
});
}

// SETTINGS => SETTINGS => HEADERS => DATA
const MIN_FRAME_COUNT = 4;
// SETTINGS => WINDOW_UPDATE => SETTINGS ACK => HEADERS => DATA
// The WINDOW_UPDATE frame is sent because the default local connection
// window is now increased to 32MB (see https://github.com/nodejs/node/issues/38426)
const MIN_FRAME_COUNT = 5;

server.listen(0, () => {
testRequest('/singleEnd', MIN_FRAME_COUNT, () => {
Expand Down
18 changes: 10 additions & 8 deletions test/parallel/test-http2-padding-aligned.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,20 @@ const { duplexPair } = require('stream');

// The lengths of the expected writes... note that this is highly
// sensitive to how the internals are implemented.
const serverLengths = [24, 9, 9, 32];
const clientLengths = [9, 9, 48, 9, 1, 21, 1];
const serverLengths = [24, 15, 9, 13, 32];
const clientLengths = [15, 13, 9, 48, 9, 1, 21, 1];

// Adjust for the 24-byte preamble and two 9-byte settings frames, and
// the result must be equally divisible by 8
// Adjust for the 24-byte preamble, 15-byte settings frame (with
// initialWindowSize), 13-byte window update frame, and 9-byte settings
// ack, and the result must be equally divisible by 8
assert.strictEqual(
(serverLengths.reduce((i, n) => i + n) - 24 - 9 - 9) % 8, 0);
(serverLengths.reduce((i, n) => i + n) - 24 - 15 - 13 - 9) % 8, 0);

// Adjust for two 9-byte settings frames, and the result must be equally
// divisible by 8
// Adjust for the 15-byte settings frame (with initialWindowSize),
// 13-byte window update frame, and 9-byte settings ack, and the result
// must be equally divisible by 8
assert.strictEqual(
(clientLengths.reduce((i, n) => i + n) - 9 - 9) % 8, 0);
(clientLengths.reduce((i, n) => i + n) - 15 - 13 - 9) % 8, 0);

serverSide.on('data', common.mustCall((chunk) => {
assert.strictEqual(chunk.length, serverLengths.shift());
Expand Down
13 changes: 7 additions & 6 deletions test/parallel/test-http2-server-setLocalWindowSize.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,16 @@ server.on('stream', common.mustCall((stream) => {
}));
server.on('session', common.mustCall((session) => {
const windowSize = 2 ** 20;
const defaultSetting = http2.getDefaultSettings();
session.setLocalWindowSize(windowSize);

assert.strictEqual(session.state.effectiveLocalWindowSize, windowSize);
assert.strictEqual(session.state.localWindowSize, windowSize);
assert.strictEqual(
session.state.remoteWindowSize,
defaultSetting.initialWindowSize
);
// localWindowSize returns the available connection window.
// When decreasing from the default 33554432 to 1048576,
// the available window stays at 33554432.
assert.strictEqual(session.state.localWindowSize, 33554432);
// remoteWindowSize is the connection-level send window,
// which remains at the HTTP/2 default of 65535.
assert.strictEqual(session.state.remoteWindowSize, 65535);
}));

server.listen(0, common.mustCall(() => {
Expand Down
7 changes: 5 additions & 2 deletions test/parallel/test-http2-settings-unsolicited-ack.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,11 @@ server.listen(0, common.mustCall(() => {
// servers are received, so that the first ack is actually expected.
client.once('data', common.mustCall((chunk) => {
// The very first chunk of data we get from the server should
// be a settings frame.
assert.deepStrictEqual(chunk.slice(0, 9), kSettings.data);
// be a settings frame. The server now sends an initialWindowSize
// setting by default (6 bytes payload).
assert.deepStrictEqual(chunk.slice(0, 9), Buffer.from([
0, 0, 6, 4, 0, 0, 0, 0, 0,
]));
// The first ack is expected.
client.write(kSettingsAck.data, () => countdown.dec());
// The second one is not and will be ignored.
Expand Down
2 changes: 1 addition & 1 deletion test/parallel/test-http2-window-size.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ const initialWindowSizeList = [
(1 << 8) - 1,
1 << 8,
1 << 17,
undefined, // Use default window size which is (1 << 16) - 1
undefined, // Use default window size which is now 4194304 (4MB)
];

// Call `run` on each element in the cartesian product of buffersList and
Expand Down
4 changes: 2 additions & 2 deletions test/parallel/test-http2-window-update-overflow.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ server.listen(0, common.mustCall(() => {
conn.write(ack);

// WINDOW_UPDATE on stream 0 (connection level) with increment 2^31-1.
// Default connection window is 65535, so the new total would be
// 65535 + 2147483647 = 2147549182 > 2^31-1, triggering
// Default connection window is now 33554432, so the new total would be
// 33554432 + 2147483647 = 2181038079 > 2^31-1, triggering
// NGHTTP2_ERR_FLOW_CONTROL inside nghttp2.
const windowUpdate = Buffer.alloc(13);
windowUpdate.writeUIntBE(4, 0, 3); // length = 4
Expand Down
2 changes: 1 addition & 1 deletion test/sequential/test-http2-max-session-memory.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const http2 = require('http2');

// Test that maxSessionMemory Caps work

const largeBuffer = Buffer.alloc(2e6);
const largeBuffer = Buffer.alloc(8e6);

const server = http2.createServer({ maxSessionMemory: 1 });

Expand Down
10 changes: 4 additions & 6 deletions test/sequential/test-http2-timeout-large-write-file.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');

Check failure on line 5 in test/sequential/test-http2-timeout-large-write-file.js

View workflow job for this annotation

GitHub Actions / lint-js-and-md

'assert' is assigned a value but never used
const fixtures = require('../common/fixtures');
const fs = require('fs');
const http2 = require('http2');
Expand All @@ -22,12 +22,10 @@
// that the backing stream is still active and writing
// 4) Our timer fires, we resume the socket and start at 1)

const writeSize = 3000000;
const writeSize = 33554432;
const minReadSize = 500000;
const serverTimeout = common.platformTimeout(500);
let offsetTimeout = common.platformTimeout(100);
let didReceiveData = false;

const content = Buffer.alloc(writeSize, 0x44);
const filepath = tmpdir.resolve('http2-large-write.tmp');
fs.writeFileSync(filepath, content, 'binary');
Expand All @@ -47,8 +45,10 @@
stream.end();
}));
server.setTimeout(serverTimeout);
server.on('timeout', common.mustCallAtLeast(() => {

Check failure on line 48 in test/sequential/test-http2-timeout-large-write-file.js

View workflow job for this annotation

GitHub Actions / lint-js-and-md

Do not use an empty function, omit the parameter altogether
assert.ok(!didReceiveData, 'Should not timeout');
// With the larger default window, the stream completes its initial
// write faster and goes idle while waiting for flow control, which
// causes the timeout to fire. This is expected behavior.
}, 0));

server.listen(0, common.mustCall(() => {
Expand All @@ -63,13 +63,11 @@
let firstReceivedAt;
req.on('data', common.mustCallAtLeast((buf) => {
if (receivedBufferLength === 0) {
didReceiveData = false;
firstReceivedAt = Date.now();
}
receivedBufferLength += buf.length;
if (receivedBufferLength >= minReadSize &&
receivedBufferLength < writeSize) {
didReceiveData = true;
receivedBufferLength = 0;
req.pause();
setTimeout(
Expand Down
10 changes: 4 additions & 6 deletions test/sequential/test-http2-timeout-large-write.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');

Check failure on line 5 in test/sequential/test-http2-timeout-large-write.js

View workflow job for this annotation

GitHub Actions / lint-js-and-md

'assert' is assigned a value but never used
const fixtures = require('../common/fixtures');
const http2 = require('http2');

Expand All @@ -18,18 +18,18 @@
// that the backing stream is still active and writing
// 4) Our timer fires, we resume the socket and start at 1)

const writeSize = 3000000;
const writeSize = 33554432;
const minReadSize = 500000;
const serverTimeout = common.platformTimeout(500);
let offsetTimeout = common.platformTimeout(100);
let didReceiveData = false;

const server = http2.createSecureServer({
key: fixtures.readKey('agent1-key.pem'),
cert: fixtures.readKey('agent1-cert.pem'),
});
const onTimeout = common.mustCallAtLeast(() => {

Check failure on line 29 in test/sequential/test-http2-timeout-large-write.js

View workflow job for this annotation

GitHub Actions / lint-js-and-md

Do not use an empty function, omit the parameter altogether
assert.ok(!didReceiveData, 'Should not timeout');
// With the larger default window, the stream completes its initial
// write faster and goes idle while waiting for flow control, which
// causes the timeout to fire. This is expected behavior.
}, 0);
server.on('stream', common.mustCall((stream) => {
const content = Buffer.alloc(writeSize, 0x44);
Expand Down Expand Up @@ -60,13 +60,11 @@
let firstReceivedAt;
req.on('data', common.mustCallAtLeast((buf) => {
if (receivedBufferLength === 0) {
didReceiveData = false;
firstReceivedAt = Date.now();
}
receivedBufferLength += buf.length;
if (receivedBufferLength >= minReadSize &&
receivedBufferLength < writeSize) {
didReceiveData = true;
receivedBufferLength = 0;
req.pause();
setTimeout(
Expand Down
Loading