Skip to content

http2: large queued request body prevents receiving an early response #66739

Open

Description

@CoffeeFlux

Version

v26.10.0

Platform

Darwin miki.local 25.6.0 Darwin Kernel Version 25.6.0: Sat Jul 11 15:27:04 PDT 2026; root:xnu-12377.161.13~4/RELEASE_ARM64_T6050 arm64

Subsystem

http2

What steps will reproduce the bug?

Save the following code into a file and run it. For the 16MB request with the default session limit, node reports ERR_HTTP2_STREAM_ERROR: Stream closed with error code NGHTTP2_ENHANCE_YOUR_CALM rather than the 413.

// A client that queues a large request body cannot read an early response.
// Cleartext HTTP/2 (h2c), no dependencies: `node repro.mjs`.
import http2 from 'node:http2';

const server = http2.createServer();
const sessions = new Set();
server.on('session', (session) => sessions.add(session));
server.on('stream', (stream) => {
  stream.on('error', () => {}); // the client may reset the stream
  // Reject without reading the request body, as a server does for a 401 or 413.
  stream.respond({ ':status': 413 });
  stream.end('payload too large');
});
await new Promise((resolve) => server.listen(0, resolve));
const origin = `http://localhost:${server.address().port}`;

const post = (bodyBytes, sessionOptions = {}) =>
  new Promise((resolve) => {
    const session = http2.connect(origin, sessionOptions);
    const req = session.request({ ':method': 'POST', ':path': '/' });
    req.on('response', (headers) => resolve(`status ${headers[':status']}`));
    req.on('error', (err) => resolve(`${err.code}: ${err.message}`));
    req.on('close', () => session.destroy());
    req.end(Buffer.alloc(bodyBytes)); // the whole body in one write
  });

const MiB = 1024 * 1024;
console.log(`node ${process.version}`);
console.log(' 8 MiB body:                   ', await post(8 * MiB));
console.log('16 MiB body:                   ', await post(16 * MiB));
console.log('16 MiB, maxSessionMemory 64 MB:', await post(16 * MiB, { maxSessionMemory: 64 }));
for (const session of sessions) session.destroy();
server.close();

How often does it reproduce? Is there a required condition?

To reproduce, more than maxSessionMemory worth of request-body data must be queued when the response arrives.

What is the expected behavior? Why is that the expected behavior?

I'd expect consistent propagation of the server's 413 responses

What do you see instead?

Opaque stream error (ERR_HTTP2_STREAM_ERROR: Stream closed with error code NGHTTP2_ENHANCE_YOUR_CALM) and no response

Additional information

No response

Activity

  1. inoway46 commented on Oct 15, 2026

    @inoway46
    Contributor

    Reproduced with a local main build (macOS x64, 38aa1f3), which reports NGHTTP2_INTERNAL_ERROR.

    Queued outbound data exceeds the session memory limit, so AddHeader() rejects response headers. The reset is deferred during receive processing, and nghttp2 queues INTERNAL_ERROR for the callback failure.

    On this build, increasing maxSessionMemory to 64 or streaming with backpressure allows the 413 response through without errors.

    @nodejs/http2 Could you clarify whether this limit should prevent receiving response headers on an existing stream?

  2. added
    http2Issues and PRs related to the http2 subsystem.
    on Oct 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    http2Issues and PRs related to the http2 subsystem.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions