From f50ef06ab6cf4b32de48bbc0193c6df97878d46f Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Mon, 27 Apr 2026 21:37:50 -0700 Subject: [PATCH] fix(http2): respect peer max concurrent streams Assisted-by: openai:gpt-5.5 Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com> --- lib/dispatcher/client-h2.js | 8 ++++++ test/h2c-client.js | 50 +++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/lib/dispatcher/client-h2.js b/lib/dispatcher/client-h2.js index 0c9ae1e0cd1..a286ae9ee33 100644 --- a/lib/dispatcher/client-h2.js +++ b/lib/dispatcher/client-h2.js @@ -233,6 +233,14 @@ function connectH2 (client, socket) { * @returns {boolean} */ busy (request) { + if (session[kRemoteSettings] === false && client[kRunning] > 0) { + return true + } + + if (client[kRunning] >= client[kMaxConcurrentStreams]) { + return true + } + if (request != null) { if (client[kRunning] > 0) { // We are already processing requests diff --git a/test/h2c-client.js b/test/h2c-client.js index eecd3991006..6c46fb417b5 100644 --- a/test/h2c-client.js +++ b/test/h2c-client.js @@ -83,6 +83,56 @@ test('Should support h2c connection with body', async t => { planner.equal(Buffer.concat(bodyChunks).toString(), 'Hello, world!') }) +test('Should queue h2c requests above the remote max concurrent streams setting', async t => { + const planner = tspl(t, { plan: 3 }) + let activeStreams = 0 + let maxActiveStreams = 0 + const paths = [] + + const server = createServer({ + settings: { + maxConcurrentStreams: 1 + } + }) + + server.on('stream', (stream, headers) => { + activeStreams++ + maxActiveStreams = Math.max(maxActiveStreams, activeStreams) + paths.push(headers[':path']) + + stream.respond({ ':status': 200 }) + setTimeout(() => { + activeStreams-- + stream.end('Hello, world!') + }, 50) + }) + + server.listen() + await once(server, 'listening') + const client = new H2CClient(`http://localhost:${server.address().port}/`, { + maxConcurrentStreams: 10, + pipelining: 10 + }) + + t.after(() => client.close()) + t.after(() => server.close()) + + const responses = await Promise.all(Array.from({ length: 5 }, async (_, i) => { + const response = await client.request({ path: `/${i}`, method: 'GET' }) + return { + statusCode: response.statusCode, + body: await response.body.text() + } + })) + + planner.strictEqual(maxActiveStreams, 1) + planner.deepStrictEqual(paths, ['/0', '/1', '/2', '/3', '/4']) + planner.deepStrictEqual(responses, Array.from({ length: 5 }, () => ({ + statusCode: 200, + body: 'Hello, world!' + }))) +}) + test('Should reject request if not h2c supported', async t => { const planner = tspl(t, { plan: 1 })