Bug Description
HTTP/2 dispatch can exceed the server's advertised SETTINGS_MAX_CONCURRENT_STREAMS
Reproducible By
import { once } from 'node:events'
import { createServer } from 'node:http2'
import { setTimeout as sleep } from 'node:timers/promises'
import { H2CClient } from 'undici'
let active = 0
let maxActive = 0
const server = createServer({
settings: { maxConcurrentStreams: 1 }
})
server.on('stream', async (stream) => {
active++
maxActive = Math.max(maxActive, active)
stream.respond({ ':status': 200 })
await sleep(100)
active--
stream.end('ok')
})
server.listen(0)
await once(server, 'listening')
const client = new H2CClient(`http://localhost:${server.address().port}`, {
maxConcurrentStreams: 10,
pipelining: 10
})
const results = await Promise.allSettled(Array.from({ length: 5 }, async (_, i) => {
const res = await client.request({ path: `/${i}`, method: 'GET' })
return { statusCode: res.statusCode, body: await res.body.text() }
}))
console.log(results.map((result) => {
if (result.status === 'fulfilled') {
return `fulfilled ${result.value.statusCode} ${result.value.body}`
}
return `rejected ${result.reason.message}`
}))
console.log({ maxActive })
await client.close()
server.close()
Expected Behavior
The client should respect the peer's advertised maxConcurrentStreams and queue additional requests instead of opening streams past the HTTP/2 session limit.
With maxConcurrentStreams: 1, all five requests should eventually complete successfully, one at a time.
Logs & Screenshots
[
'fulfilled 200 ok',
'rejected Stream closed with error code NGHTTP2_REFUSED_STREAM',
'rejected Stream closed with error code NGHTTP2_REFUSED_STREAM',
'rejected Stream closed with error code NGHTTP2_REFUSED_STREAM',
'rejected Stream closed with error code NGHTTP2_REFUSED_STREAM'
]
{ maxActive: 1 }
Environment
macOS 26.4.1
Node v24.15.0
undici v8.1.0
Bug Description
HTTP/2 dispatch can exceed the server's advertised
SETTINGS_MAX_CONCURRENT_STREAMSReproducible By
Expected Behavior
The client should respect the peer's advertised
maxConcurrentStreamsand queue additional requests instead of opening streams past the HTTP/2 session limit.With
maxConcurrentStreams: 1, all five requests should eventually complete successfully, one at a time.Logs & Screenshots
Environment
macOS 26.4.1
Node v24.15.0
undici v8.1.0