Skip to content

[v24.x backport] http2: send deferred RST_STREAM on stream destroy - #67445

Draft
gabedemesa wants to merge 2 commits into
nodejs:v24.x-stagingfrom
gabedemesa:backport-67372-to-v24.x
Draft

gabedemesa wants to merge 2 commits into
nodejs:v24.x-stagingfrom
gabedemesa:backport-67372-to-v24.x

Conversation

@gabedemesa

@gabedemesa gabedemesa commented Nov 12, 2026 •

Copy link
Copy Markdown

Backport of #67372 to v24.x.

#67372 has not landed yet; this draft is based on its current commit (7288a16) and will be updated with the landed commit, including its PR-URL and Reviewed-By metadata, once it merges.

The cherry-pick conflicted in lib/internal/http2/core.js, because onStreamClose() and closeStream() were restructured on main after v24.x branched. The same changes are applied to the v24.x versions of those functions:

  • STREAM_FLAGS_RST_DEFERRED is set where closeStream() defers finishCloseStream to 'finish', and cleared in finishCloseStream().
  • Http2Stream#_destroy() submits the deferred RST_STREAM when the stream is already closed with the flag set.
  • onStreamClose() clears the flag before destroying. On main this is done only in the locallyInitiated branch; on v24.x it is done unconditionally, which is equivalent because the flag can only be set on a stream that closeStream() has already marked closed.

The src/node_http2.cc change and both tests applied cleanly. Authorship is kept with the original author.

The tests needed one adjustment, in a separate fixup! commit. On main, a stream that receives a clean RST_STREAM before its body ends errors with ERR_HTTP2_STREAM_ABORTED; on v24.x it closes without an error. The tests expected the error, so on v24.x they expect no error on the receiving side instead. Both sides still close with rstCode 0, as the tests assert.

Verification (macOS arm64, local build of this branch)

  • test-http2-close-destroy-queued-body and test-http2-server-close-destroy-queued-body pass 50 out of 50 runs each.
  • Without the fix (lib/internal/http2/core.js and src/node_http2.cc reverted), both tests run away, reaching 1,756 MB and 1,936 MB of RSS before being stopped.
  • parallel/test-http2-* and sequential/test-http2-* pass.
  • ESLint and cpplint are clean for the changed files.
  • The reproductions from HTTP/2: client session outgoing queue grows without bound (Http2Session::CopyDataIntoOutgoing), OOM-killing the process undici#6181 stay bounded: fetch() uploads aborted mid-body, a server that holds the flow-control window until the client's AbortSignal.timeout() fires, and plain node:http2 close() followed by destroy() (respectively 207 MB, 63 MB, under 1 MB).

🤖 Generated with Claude Code

close() with NO_ERROR waits for 'finish' before sending RST_STREAM.
If the body is stuck behind flow control and the stream is destroyed
first, RST_STREAM is never sent and nghttp2 keeps the stream open.
On the next WINDOW_UPDATE, OnRead finds no stream and returns 0
without EOF, so nghttp2 sends empty DATA frames until OOM. Both
client and server streams are affected.

Send the deferred RST_STREAM in _destroy(). As a backstop, return
NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE from OnRead when the stream is
gone, which nghttp2 documents as the way to reset a stream on error.

Fixes: nodejs#67363
Signed-off-by: inoway46 <inoueyuya416@gmail.com>
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/http2
  • @nodejs/net

@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. http2 Issues and PRs related to the http2 subsystem. needs-ci PRs that need a full CI run. v24.x Issues that can be reproduced on v24.x or PRs targeting the v24.x-staging branch. labels Nov 12, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Welcome to Node.js, and thank you for your first contribution!

Before review, please take a moment to read:

Please make sure every commit is signed off. For a first pull request, GitHub Actions require collaborator approval and Jenkins CI must be started by a collaborator or triager, so an initial wait is normal.

On v24.x, a clean RST_STREAM received before the body ends closes the
stream without an error, unlike main, where it errors with
ERR_HTTP2_STREAM_ABORTED. Expect no error on the receiving side. The
tests still run out of memory without the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: gdemesa <gdemesa@opengov.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++ Issues and PRs that require attention from people who are familiar with C++. http2 Issues and PRs related to the http2 subsystem. needs-ci PRs that need a full CI run. v24.x Issues that can be reproduced on v24.x or PRs targeting the v24.x-staging branch.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants