Skip to content

Commit 70882db

Browse files
committed
http2: increase default window sizes
Increase the default HTTP/2 stream window from 64KB (65535) to 4MB (4194304) and the default local connection window to 32MB (33554432). The default 64KB window limits throughput on high-latency connections to window_size / RTT. With a 250ms RTT, throughput is limited to 256KB/s. The new defaults improve throughput to 16MB/s (128Mbps) for the stream window and 128MB/s (1Gbps) for the connection window. Fixes: #38426 Signed-off-by: Matteo Collina <hello@matteocollina.com>
1 parent ace91de commit 70882db

16 files changed

Lines changed: 94 additions & 49 deletions

doc/api/http2.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3650,7 +3650,7 @@ properties.
36503650
permitted on the `Http2Session` instances. **Default:** `true`.
36513651
* `initialWindowSize` {number} Specifies the _sender's_ initial window size in
36523652
bytes for stream-level flow control. The minimum allowed value is 0. The
3653-
maximum allowed value is 2<sup>32</sup>-1. **Default:** `65535`.
3653+
maximum allowed value is 2<sup>32</sup>-1. **Default:** `4194304`.
36543654
* `maxFrameSize` {number} Specifies the size in bytes of the largest frame
36553655
payload. The minimum allowed value is 16,384. The maximum allowed value is
36563656
2<sup>24</sup>-1. **Default:** `16384`.

lib/internal/http2/core.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1205,7 +1205,16 @@ function setupHandle(socket, type, options) {
12051205
}
12061206

12071207
const settings = typeof options.settings === 'object' ?
1208-
options.settings : {};
1208+
{ ...options.settings } : {};
1209+
1210+
// Increase the default initial window size to improve throughput
1211+
// on high-latency connections. The HTTP/2 default of 65535 (64KB)
1212+
// limits throughput to window_size / RTT. By increasing to 4MB,
1213+
// throughput is significantly improved.
1214+
// See https://github.com/nodejs/node/issues/38426
1215+
if (settings.initialWindowSize === undefined) {
1216+
settings.initialWindowSize = constants.DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE;
1217+
}
12091218

12101219
this.settings(settings);
12111220

src/node_http2.cc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,17 @@ Http2Session::Http2Session(Http2State* http2_state,
612612
&alloc_info), 0);
613613
session_.reset(session);
614614

615+
// Increase the default local connection window to improve throughput
616+
// on high-latency connections. The default 64KB window limits throughput
617+
// to window_size / RTT. With a 32MB connection window, throughput is
618+
// significantly improved. See https://github.com/nodejs/node/issues/38426
619+
CHECK_EQ(nghttp2_session_set_local_window_size(
620+
session,
621+
NGHTTP2_FLAG_NONE,
622+
0,
623+
DEFAULT_SETTINGS_LOCAL_CONNECTION_WINDOW_SIZE),
624+
0);
625+
615626
outgoing_storage_.reserve(1024);
616627
outgoing_buffers_.reserve(32);
617628

src/node_http2.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,18 @@ constexpr uint64_t kDefaultMaxSessionMemory = 10000000;
4343
constexpr uint32_t DEFAULT_SETTINGS_HEADER_TABLE_SIZE = 4096;
4444
constexpr uint32_t DEFAULT_SETTINGS_ENABLE_PUSH = 1;
4545
constexpr uint32_t DEFAULT_SETTINGS_MAX_CONCURRENT_STREAMS = 0xffffffffu;
46-
constexpr uint32_t DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE = 65535;
46+
constexpr uint32_t DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE = 4194304;
4747
constexpr uint32_t DEFAULT_SETTINGS_MAX_FRAME_SIZE = 16384;
4848
constexpr uint32_t DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE = 65535;
4949
constexpr uint32_t DEFAULT_SETTINGS_ENABLE_CONNECT_PROTOCOL = 0;
5050
constexpr uint32_t MAX_MAX_FRAME_SIZE = 16777215;
5151
constexpr uint32_t MIN_MAX_FRAME_SIZE = DEFAULT_SETTINGS_MAX_FRAME_SIZE;
5252
constexpr uint32_t MAX_INITIAL_WINDOW_SIZE = 2147483647;
5353

54+
// Default local connection window size (32MB) to improve throughput
55+
// on high-latency connections. See https://github.com/nodejs/node/issues/38426
56+
constexpr uint32_t DEFAULT_SETTINGS_LOCAL_CONNECTION_WINDOW_SIZE = 33554432;
57+
5458
// Stream is not going to have any DATA frames
5559
constexpr int STREAM_OPTION_EMPTY_PAYLOAD = 0x1;
5660

test/parallel/test-http2-binding.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const settings = http2.getDefaultSettings();
1717
assert.strictEqual(settings.headerTableSize, 4096);
1818
assert.strictEqual(settings.enablePush, true);
1919
assert.strictEqual(settings.maxConcurrentStreams, 4294967295);
20-
assert.strictEqual(settings.initialWindowSize, 65535);
20+
assert.strictEqual(settings.initialWindowSize, 4194304);
2121
assert.strictEqual(settings.maxFrameSize, 16384);
2222

2323
assert.strictEqual(binding.nghttp2ErrorString(-517),
@@ -239,7 +239,7 @@ const defaultSettings = {
239239
DEFAULT_SETTINGS_HEADER_TABLE_SIZE: 4096,
240240
DEFAULT_SETTINGS_ENABLE_PUSH: 1,
241241
DEFAULT_SETTINGS_MAX_CONCURRENT_STREAMS: 4294967295,
242-
DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE: 65535,
242+
DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE: 4194304,
243243
DEFAULT_SETTINGS_MAX_FRAME_SIZE: 16384,
244244
DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE: 65535,
245245
DEFAULT_SETTINGS_ENABLE_CONNECT_PROTOCOL: 0

test/parallel/test-http2-client-setLocalWindowSize.js

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -73,15 +73,16 @@ const http2 = require('http2');
7373

7474
client.on('connect', common.mustCall(() => {
7575
const windowSize = 2 ** 20;
76-
const defaultSetting = http2.getDefaultSettings();
7776
client.setLocalWindowSize(windowSize);
7877

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

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

102103
client.on('connect', common.mustCall(() => {
103104
const windowSize = 20;
104-
const defaultSetting = http2.getDefaultSettings();
105105
client.setLocalWindowSize(windowSize);
106106

107107
assert.strictEqual(client.state.effectiveLocalWindowSize, windowSize);
108-
assert.strictEqual(
109-
client.state.localWindowSize,
110-
defaultSetting.initialWindowSize
111-
);
112-
assert.strictEqual(
113-
client.state.remoteWindowSize,
114-
defaultSetting.initialWindowSize
115-
);
108+
// localWindowSize returns the available connection window.
109+
// When decreasing from the default 33554432 to 20,
110+
// the available window stays at 33554432.
111+
assert.strictEqual(client.state.localWindowSize, 33554432);
112+
// remoteWindowSize is the connection-level send window,
113+
// which remains at the HTTP/2 default of 65535.
114+
assert.strictEqual(client.state.remoteWindowSize, 65535);
116115

117116
server.close();
118117
client.close();

test/parallel/test-http2-getpackedsettings.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const http2 = require('http2');
99
const check = Buffer.from([0x00, 0x01, 0x00, 0x00, 0x10, 0x00,
1010
0x00, 0x02, 0x00, 0x00, 0x00, 0x01,
1111
0x00, 0x03, 0xff, 0xff, 0xff, 0xff,
12-
0x00, 0x04, 0x00, 0x00, 0xff, 0xff,
12+
0x00, 0x04, 0x00, 0x40, 0x00, 0x00,
1313
0x00, 0x05, 0x00, 0x00, 0x40, 0x00,
1414
0x00, 0x06, 0x00, 0x00, 0xff, 0xff,
1515
0x00, 0x08, 0x00, 0x00, 0x00, 0x00]);

test/parallel/test-http2-pack-end-stream-flag.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,10 @@ function testRequest(path, targetFrameCount, callback) {
5252
});
5353
}
5454

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

5860
server.listen(0, () => {
5961
testRequest('/singleEnd', MIN_FRAME_COUNT, () => {

test/parallel/test-http2-padding-aligned.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,20 @@ const { duplexPair } = require('stream');
2525

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

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

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

4143
serverSide.on('data', common.mustCall((chunk) => {
4244
assert.strictEqual(chunk.length, serverLengths.shift());

test/parallel/test-http2-server-setLocalWindowSize.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,16 @@ server.on('stream', common.mustCall((stream) => {
1414
}));
1515
server.on('session', common.mustCall((session) => {
1616
const windowSize = 2 ** 20;
17-
const defaultSetting = http2.getDefaultSettings();
1817
session.setLocalWindowSize(windowSize);
1918

2019
assert.strictEqual(session.state.effectiveLocalWindowSize, windowSize);
21-
assert.strictEqual(session.state.localWindowSize, windowSize);
22-
assert.strictEqual(
23-
session.state.remoteWindowSize,
24-
defaultSetting.initialWindowSize
25-
);
20+
// localWindowSize returns the available connection window.
21+
// When decreasing from the default 33554432 to 1048576,
22+
// the available window stays at 33554432.
23+
assert.strictEqual(session.state.localWindowSize, 33554432);
24+
// remoteWindowSize is the connection-level send window,
25+
// which remains at the HTTP/2 default of 65535.
26+
assert.strictEqual(session.state.remoteWindowSize, 65535);
2627
}));
2728

2829
server.listen(0, common.mustCall(() => {

0 commit comments

Comments
 (0)