Skip to content

node:http2: add the goawayCode and goawayLastStreamID session getters - #37550

Merged
alii merged 1 commit into
mainfrom
farm/50f2c7d0/http2-goaway-getters
Aug 11, 2026
Merged

node:http2: add the goawayCode and goawayLastStreamID session getters#37550
alii merged 1 commit into
mainfrom
farm/50f2c7d0/http2-goaway-getters

Conversation

@robobun

@robobun robobun commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Repro

const http2 = require("node:http2");
const server = http2.createServer();
server.on("session", s => console.log("server:", s.goawayCode, s.goawayLastStreamID));
server.on("stream", stream => stream.session.goaway(http2.constants.NGHTTP2_ENHANCE_YOUR_CALM, 1));
server.listen(0, "127.0.0.1", () => {
  const client = http2.connect(`http://127.0.0.1:${server.address().port}`);
  client.on("error", () => {});
  client.on("connect", () => {
    console.log("client:", client.goawayCode, client.goawayLastStreamID);
    client.request({ ":path": "/" }).on("error", () => {});
  });
  client.on("goaway", (code, lastStreamID) =>
    console.log("event:", code, lastStreamID, "getters:", client.goawayCode, client.goawayLastStreamID),
  );
  client.on("close", () => {
    console.log("after close:", client.goawayCode, client.goawayLastStreamID);
    server.close();
  });
});

node v26.3.0 (and this branch):

server: 0 0
client: 0 0
event: 11 1 getters: 11 1
after close: 11 1

bun 1.4.0 and main:

server: undefined undefined
client: undefined undefined
event: 11 1 getters: undefined undefined
after close: undefined undefined

Cause

Node's Http2Session (lib/internal/http2/core.js) has two getters describing the GOAWAY frame the session received:

get goawayCode()         { return this[kState].goawayCode || NGHTTP2_NO_ERROR; }
get goawayLastStreamID() { return this[kState].goawayLastStreamID || 0; }

onGoawayData fills both in before emitting 'goaway', and they keep their values after the session is destroyed. Neither ClientHttp2Session nor ServerHttp2Session in src/js/node/http2.ts defined them. The received code was already tracked (kGoawayCode, used to pick the rst code of streams torn down by the GOAWAY); the received Last-Stream-ID was passed to the event and then dropped.

Fix

Store the received Last-Stream-ID next to the code in both onGoAway handlers (before 'goaway' is emitted, as in node) and define the two getters once on the shared Http2Session base class, where node defines them. They describe received GOAWAYs only: a goaway() this side sends does not touch them, same as node.

This is a plain node compat gap. Code written for node that reads these (for example session.goawayCode !== NGHTTP2_NO_ERROR to tell a graceful shutdown from an errored one) gets undefined on bun today, so such a check treats every session as errored. No type or docs changes: node does not document these getters and @types/node does not declare them, they are just part of the Http2Session surface.

Verification

Four tests added to test/js/node/http2/node-http2.test.js (describe "http2 session goawayCode / goawayLastStreamID"). All four fail on bun 1.4.0 and pass with this change; the same scenarios run as a standalone script under node v26.3.0 and under the fixed debug build print identical output.

  • both properties are getters (no setter) and report 0 / 0 on a fresh client and server session
  • server sends goaway(ENHANCE_YOUR_CALM, 1): the client reports 11 / 1 inside the 'goaway' event and still after it was destroyed; the server's own getters stay 0 / 0
  • client sends goaway(CANCEL, 2): the server session reports 8 / 2, with the same persistence check
  • server sends a bare goaway(): the client reports 0 / 1 (the implicit last processed stream id)

With the debug build, node-http2.test.js (351 pass, 6 skip), h2-conformance.test.ts and the vendored node http2 goaway/shutdown tests pass.

Node's Http2Session exposes the error code and Last-Stream-ID of the
GOAWAY frame a session received as goawayCode and goawayLastStreamID,
reporting 0 for both until one arrives. Bun's client and server
sessions had neither property.

The received code was already kept under kGoawayCode; store the
received Last-Stream-ID alongside it in both onGoAway handlers and
define the two getters on the shared Http2Session base class, where
node defines them.
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 4 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: abacc22e-1e52-4563-a645-9cbd9cdc30c4

📥 Commits

Reviewing files that changed from the base of the PR and between da3851e and 6dfe32f.

📒 Files selected for processing (2)
  • src/js/node/http2.ts
  • test/js/node/http2/node-http2.test.js

Comment @coderabbitai help to get the list of available commands.

@robobun

robobun commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 10:33 AM PT - Aug 11th, 2026

@robobun, your commit 6dfe32f is building: #92356

@robobun

robobun commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator Author

Status: merged.

Reproduced on bun 1.4.0 and on main (da3851e) with the script in the PR body: both session classes returned undefined for goawayCode / goawayLastStreamID, node v26.3.0 returns 0 / 0 and then the received frame's values. The four new tests in test/js/node/http2/node-http2.test.js fail on 1.4.0 and pass with this change. No upstream node test references these getters (see the comment below), so nothing from the upstream suite becomes addable because of this.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM — straightforward Node.js compat addition mirroring node's Http2Session getters.

What was reviewed:

  • Getters on the shared Http2Session base use || NGHTTP2_NO_ERROR / || 0, matching node's lib/internal/http2/core.js exactly; NGHTTP2_NO_ERROR is destructured into module scope at http2.ts:1747 so it's in scope.
  • Both onGoAway handlers (server + client) set kGoawayLastStreamID before emitting 'goaway', alongside the pre-existing kGoawayCode write — no sibling site missed.
  • Tests cover both directions, initial state, getter descriptors, persistence after destroy, and graceful GOAWAY; they use port: 0, local servers, describe.concurrent, and try/finally cleanup.
Extended reasoning...

Overview

This PR adds two getters, goawayCode and goawayLastStreamID, to the shared Http2Session base class in src/js/node/http2.ts, plus a new kGoawayLastStreamID symbol and one assignment in each of the two goaway native-callback handlers (server and client). ~10 lines of production code and ~130 lines of tests in test/js/node/http2/node-http2.test.js.

Security risks

None. This exposes read-only getters over state that was already passed to the userland 'goaway' event; no new inputs are parsed, no auth/crypto/permissions paths touched.

Level of scrutiny

Low-to-moderate. This is a pure Node.js API-surface compat gap: node defines these getters on Http2Session and bun did not. The implementation is a direct transcription of node's (this[kState].goawayCode || NGHTTP2_NO_ERROR, etc.). The kGoawayCode symbol was already being set in exactly the same place for stream teardown; this PR adds the sibling kGoawayLastStreamID write next to it and surfaces both via getters. No behavior change to existing code paths — the new symbol has no other readers.

Other factors

  • Verified NGHTTP2_NO_ERROR is a bare module-scope const (destructured at http2.ts:1747), so the getter body resolves it.
  • Checked that both native-callback goaway handlers (ServerHttp2Session at :4462 and ClientHttp2Session at :5458) received the symmetric change — the "fix the whole class" rule is satisfied.
  • The || fallback (vs ??) matches node verbatim and is observably equivalent here since the fallback values (0) equal the only falsy value the fields can hold.
  • Tests follow harness conventions: describe.concurrent, port: 0, awaiting real events (no sleeps), try/finally cleanup registered before assertions, and assert the strongest invariant (exact getter values inside the event, after destroy, and that the sending side's getters stay 0/0). The descriptor test walks the prototype chain rather than assuming placement, so it's robust to where bun vs node define the accessor.
  • No CODEOWNERS entry for this file. No outstanding reviewer comments.

@alii
alii merged commit 108e412 into main Aug 11, 2026
51 checks passed
@alii
alii deleted the farm/50f2c7d0/http2-goaway-getters branch August 11, 2026 21:28
@alii

alii commented Aug 11, 2026

Copy link
Copy Markdown
Member

@robobun are there any tests upstream in the Node.js test suite we can add now that pass?

@robobun

robobun commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator Author

Checked, and the answer is no for this change specifically: nothing in node's test suite reads goawayCode or goawayLastStreamID. The only file in all of nodejs/node that mentions either name is lib/internal/http2/core.js itself (they are not in doc/api/http2.md either), so no upstream test changes status because of these getters. The cases in node-http2.test.js are the only coverage.

Since I was looking anyway, I diffed upstream test/{parallel,sequential}/test-http2-* at v26.3.0 against what is vendored in test/js/node/test/ and ran the 17 files we do not carry against the current build. None of them is a new candidate:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants