Skip to content

HTTP/2 stream timeouts decrement session open-stream count twice #5073

@trivikr

Description

@trivikr

Bug Description

HTTP/2 stream timeouts decrement session open-stream count twice

Reproducible By

  1. Start an HTTP/2 TLS server that sends response headers immediately but delays the response body longer than the client's bodyTimeout.
  2. Create an Undici Client and a small bodyTimeout such as 50.
  3. Send a request and read res.body.text() so the request hits the HTTP/2 stream timeout path.
  4. Inspect client[kHTTP2Session] after the timeout and read the session symbol whose description is open streams.
  5. Observe that the counter is -1 after a single timed-out request.

Minimal repro

import { createSecureServer } from 'node:http2'
import { once } from 'node:events'
import pem from '@metcoder95/https-pem'
import { Client } from 'undici'
import symbols from 'undici/lib/core/symbols.js'

const { kHTTP2Session } = symbols

const server = createSecureServer(await pem.generate({ opts: { keySize: 2048 } }))

server.on('stream', (stream) => {
  stream.respond({ ':status': 200, 'content-type': 'text/plain' })
  setTimeout(() => stream.end('late body'), 300)
})

await once(server.listen(0), 'listening')

const client = new Client(`https://localhost:${server.address().port}`, {
  connect: { rejectUnauthorized: false },
  bodyTimeout: 50
})

const res = await client.request({ path: '/', method: 'GET' })

try {
  await res.body.text()
} catch (err) {
  console.log('request error:', err.message)
}

const session = client[kHTTP2Session]
const openStreams = Object.getOwnPropertySymbols(session)
  .find((sym) => sym.description === 'open streams')

console.log('open streams after timeout:', session[openStreams])

await client.close()
await new Promise((resolve) => server.close(resolve))

Expected Behavior

A timed-out HTTP/2 request should decrement session[kOpenStreams] exactly once, leaving the counter at 0 after the stream is closed. The timeout path should not underflow the counter or leave future stream accounting inconsistent.

Logs & Screenshots

Output for repro

request error: HTTP/2: "stream timeout after 50"
open streams after timeout: -1

Environment

macOS 26.4.1
Node v24.14.1
undici v8.1.0

Metadata

Metadata

Assignees

No one assigned

    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