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
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions