Default pipelining to maxConcurrentStreams with allowH2 (original) (raw)

Bug Description

The recent H2CClient work (#4118) does it well. If pipelining is not set, it defaults to maxConcurrentStreams which makes sense. This however, is not a case when a regular Client (or its descendent Agent) is in use.

Being on a HTTP/2 connection is still limited to either the default (1) or requires manual setup.
Manual setup is inconvenient and confusing. Pipelining is a HTTP/1.1 concept.

Docs say nothing about adjust pipelining when using HTTP/2 multiplexing and actually link to the HTTP/1.1 RFC under pipelining.

Reproducible By

sever.js

import { fetch, Agent, setGlobalDispatcher, interceptors } from "undici"; import { createServer } from "node:http"; import { once } from "node:events";

setGlobalDispatcher( new Agent({ allowH2: true, connections: 10, //pipelining: 100, }).compose([interceptors.dns(), interceptors.retry()]), );

const server = createServer((req, res) => { fetch("https://my-https2-server.com", { signal: AbortSignal.timeout(1_000), }) .then((result) => { return Array.fromAsync(result.body); }) .then(() => { res.end(""); }) .catch((err) => { res.writeHead(500); res.end("internal server error"); }); }).listen("3000");

await once(server, "listening"); console.log("server started on port 3000");

Then run the server:

And load test it. Run the same with and without pipelining set.

hey -n 15000 -c 100 -q 300 -t "1" http://localhost:3000

Expected Behavior

Same performance with or without pipelining set.

Environment

Node 23, macOS 15.3, but it's not platform related, it seems.

Additional context

Should #2750 make HTTP/2 on by default, users will not benefit from HTTP/2 at all - it might behave pretty much the same as HTTP/1.1 connections with keep-alive + some TLS overhead...