Skip to content

HTTP/2 dispatch can exceed the server's advertised SETTINGS_MAX_CONCURRENT_STREAMS #5134

@trivikr

Description

@trivikr

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions