Skip to content

nghttp3: a message's fields are no longer capped at 64, so a response with more no longer silently loses the rest - #516

Open
MDA2AV wants to merge 1 commit into
mainfrom
fix/506-nghttp3-many-response-fields
Open

MDA2AV wants to merge 1 commit into
mainfrom
fix/506-nghttp3-many-response-fields

Conversation

@MDA2AV

@MDA2AV MDA2AV commented Nov 5, 2026 •

Copy link
Copy Markdown
Owner

What was wrong

ih3_unpack_headers filled a fixed nghttp3_nv nva[64], stopped when it was full, and returned what it had as if that were everything. A response past 64 fields, :status included, lost the rest with no error and nothing logged: of 70 headers, the client got 63.

The test client goes through the same function, so its requests were cut at 64 fields too.

Details are in #506.

The fix

The array belongs to the connection now, and grows as needed. ih3_unpack_headers fills it in one pass, nghttp3 copies out of it during the submit, and ih3_free frees it. A message within the first 64 fields costs nothing new but one malloc per connection.

Native libraries

runtimes/linux-x64 and runtimes/linux-arm64 are rebuilt with scripts/build-nghttp3-native.sh at its pinned nghttp3 ref: x64 on the host, arm64 with the aarch64-linux-gnu cross toolchain in an Ubuntu 24.04 container. Both still need nothing past GLIBC 2.17.

Main's x64 library was built with another work directory, which nghttp3's assert strings embed. So besides this change the binary's diff has those paths, and a rebuild with the script's default now reproduces it.

Behaviour

A response past 64 fields now reaches the peer whole, on both the buffered and streamed paths.

API

None.

Tests

New in the E2E suite (Nghttp3ManyFieldsTests.cs), through the bare HTTP/3 test client:

  • nghttp3: a buffered response with 63 headers of its own reaches the peer whole, and with 64 and 200
  • nghttp3: a streamed response with 63 headers of its own reaches the peer whole, and with 64 and 200
  • nghttp3: a request past 64 fields reaches its handler whole

Against main's library (the tests only):

PASS  nghttp3: a buffered response with 63 headers of its own reaches the peer whole
PASS  nghttp3: a streamed response with 63 headers of its own reaches the peer whole
FAIL  nghttp3: a buffered response with 64 headers of its own reaches the peer whole: expected [64], got [63]
FAIL  nghttp3: a streamed response with 64 headers of its own reaches the peer whole: expected [64], got [63]
FAIL  nghttp3: a buffered response with 200 headers of its own reaches the peer whole: expected [200], got [63]
FAIL  nghttp3: a streamed response with 200 headers of its own reaches the peer whole: expected [200], got [63]
FAIL  nghttp3: a request past 64 fields reaches its handler whole: expected [70], got [60]
2 passed, 5 failed, 0 skipped

With the fix:

  • Unit: 145 passed.
  • E2E: 460 passed.
  • Chaos: 83 passed.
  • Http: 95 passed.

Bench

Measured with the same setup as #486:

  • One reactor pinned to CPU 6, and h2load -c 32 -m 16 -t 1 --alpn-list=h3 pinned to CPU 2.
  • Main, this branch, and main again from a second worktree as a control.
  • Six interleaved rounds of 10 s; each build is compared with main inside its own round.
Sample Server CPU per request, branch vs main Control vs main
Http3/Nghttp3Buffered +0.3% +0.3%

The same as the control. A connection gains one malloc of 2.5 KiB, at its first message.

Fixes #506.

… with more no longer silently loses the rest

The shim packed fields into a fixed array, stopped when it was full and returned what it had as if that were everything. The array is the connection's now and grows as needed.

Fixes #506.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

nghttp3: a response past 64 fields silently loses the rest

1 participant