Files
node/test/parallel/test-http2-window-update-overflow.js
Matteo Collina 1576cb83b4 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: https://github.com/nodejs/node/issues/38426
Signed-off-by: Matteo Collina <hello@matteocollina.com>
PR-URL: https://github.com/nodejs/node/pull/64623
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
Reviewed-By: Tim Perry <pimterry@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
Reviewed-By: Ethan Arrowood <ethan@arrowood.dev>
Reviewed-By: Aviv Keller <me@aviv.sh>
Reviewed-By: Robert Nagy <ronagy@icloud.com>
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
2026-08-04 14:01:33 +00:00

85 lines
2.8 KiB
JavaScript

'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const http2 = require('http2');
const net = require('net');
// Regression test: a connection-level WINDOW_UPDATE that causes the flow
// control window to exceed 2^31-1 must destroy the Http2Session (not leak it).
//
// nghttp2 responds with GOAWAY(FLOW_CONTROL_ERROR) internally but previously
// Node's OnInvalidFrame callback only propagated errors for
// NGHTTP2_ERR_STREAM_CLOSED and NGHTTP2_ERR_PROTO. The missing
// NGHTTP2_ERR_FLOW_CONTROL case left the session unreachable after the GOAWAY,
// causing a memory leak.
const server = http2.createServer();
server.on('session', common.mustCall((session) => {
session.on('error', common.mustCall());
session.on('close', common.mustCall(() => server.close()));
}));
server.listen(0, common.mustCall(() => {
const conn = net.connect({
port: server.address().port,
allowHalfOpen: true,
});
// HTTP/2 client connection preface.
conn.write('PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n');
// Empty SETTINGS frame (9-byte header, 0-byte payload).
const settingsFrame = Buffer.alloc(9);
settingsFrame[3] = 0x04; // type: SETTINGS
conn.write(settingsFrame);
let inbuf = Buffer.alloc(0);
let state = 'settingsHeader';
let settingsFrameLength;
conn.on('data', (chunk) => {
inbuf = Buffer.concat([inbuf, chunk]);
switch (state) {
case 'settingsHeader':
if (inbuf.length < 9) return;
settingsFrameLength = inbuf.readUIntBE(0, 3);
inbuf = inbuf.slice(9);
state = 'readingSettings';
// Fallthrough
case 'readingSettings': {
if (inbuf.length < settingsFrameLength) return;
inbuf = inbuf.slice(settingsFrameLength);
state = 'done';
// ACK the server SETTINGS.
const ack = Buffer.alloc(9);
ack[3] = 0x04; // type: SETTINGS
ack[4] = 0x01; // flag: ACK
conn.write(ack);
// WINDOW_UPDATE on stream 0 (connection level) with increment 2^31-1.
// 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
windowUpdate[3] = 0x08; // type: WINDOW_UPDATE
windowUpdate[4] = 0x00; // flags: none
windowUpdate.writeUIntBE(0, 5, 4); // stream id: 0
windowUpdate.writeUIntBE(0x7FFFFFFF, 9, 4); // increment: 2^31-1
conn.write(windowUpdate);
}
}
});
// The server must close the connection after sending GOAWAY.
conn.on('end', common.mustCall(() => conn.end()));
conn.on('close', common.mustCall());
}));