Skip to content

http2: don't suppress internal termination detection on a peer GOAWAY - #66641

Open
mcollina wants to merge 1 commit into
nodejs:mainfrom
mcollina:http2-fix-goaway-detector
Open

mcollina wants to merge 1 commit into
nodejs:mainfrom
mcollina:http2-fix-goaway-detector

Conversation

@mcollina

@mcollina mcollina commented Oct 10, 2026 •

Copy link
Copy Markdown
Member

When a peer sends GOAWAY(NO_ERROR), core.js calls session.close(), which sets graceful_close_initiated_ and goaway_initiated_ without any application code. That disarmed the detection of nghttp2 terminating the session internally after a protocol error, leaving open streams and the connection hanging.

As suggested by @pimterry, this also treats it as an internal termination when nghttp2 no longer wants to read or write (want_read() == 0 && want_write() == 0) while we still track open streams. A test covers the peer GOAWAY followed by an oversized frame case.


AI generated, reviewed by me.

@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. labels Oct 10, 2026
@mcollina
mcollina requested a review from pimterry October 10, 2026 10:14
@codecov

codecov Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.50%. Comparing base (df2ea49) to head (1477f70).
⚠️ Report is 3 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #66641      +/-   ##
==========================================
- Coverage   90.51%   90.50%   -0.02%     
==========================================
  Files         797      797              
  Lines      278252   278263      +11     
  Branches    53472    53466       -6     
==========================================
- Hits       251868   251830      -38     
- Misses      16761    16808      +47     
- Partials     9623     9625       +2     
Files with missing lines Coverage Δ
src/node_http2.cc 82.17% <100.00%> (+0.13%) ⬆️
src/node_http2.h 92.47% <ø> (+0.08%) ⬆️

... and 39 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@pimterry pimterry left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the tests are failing.

The current approach here has some clear false negatives, e.g. if we manually set the same error code that nghttp2 generates internally, we'd still hit the original issue. I'm not sure trying distinguish specific frames more precisely than "completely unexpected" is going to work well.

I think there's a better approach: nghttp2 says given want_read() == 0 && want_write() == 0, the application should drop the connection, and we could use that to detect this issue instead. In all of the cases we're looking at here, we'll reach that state unexpectedly. So in any scenario where we think we have an open stream on a session, but nghttp2 says the session is dead => an internal error must have happened somewhere.

Something like (AI gen):

bool Http2Session::HasStreamsAbandonedByNghttp2() const {
  if (HasPendingData()) return false;
  return std::ranges::any_of(streams_, [](const auto& entry) {
    return !entry.second->is_closed() && !entry.second->is_destroyed();
  });
}

and then make the check at the end of SendPendingData into:

if (internal_goaway_sent_ || HasStreamsAbandonedByNghttp2()) {
...

I'm surprised the description says this isn't testable - it looks very reachable I think, and Claude whipped up a few examples that seem to hit this immediately.

When a peer sends GOAWAY(NO_ERROR), the session starts a graceful close
on its own, which disarmed the detection of nghttp2 internally
terminating the session after a protocol error. Instead, also treat it
as an internal termination when nghttp2 no longer wants to read or
write while we still have open streams.

Signed-off-by: Matteo Collina <hello@matteocollina.com>
@mcollina
mcollina force-pushed the http2-fix-goaway-detector branch from 8cf2198 to 1477f70 Compare October 20, 2026 14:09

@pimterry pimterry left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! LGTM 👍

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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants