Skip to content

Reduce H2 header allocations - #13420

Open
zwoop wants to merge 5 commits into
apache:masterfrom
zwoop:H2Perf
Open

Reduce H2 header allocations#13420
zwoop wants to merge 5 commits into
apache:masterfrom
zwoop:H2Perf

Conversation

@zwoop

@zwoop zwoop commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

This reduces per-request HTTP/2 overhead in two independent steps. First, the HPACK header-block decode happens in place when the whole block is contiguous in the frame reader (avoiding a per-request malloc+memcpy+free), and HEADERS-frame encoding uses a stack buffer for typical header sizes. Second — the larger win — the decoded request header is handed directly to HttpSM instead of being serialized and reparsed: the 2→1.1 converter now normalizes the URL (splitting host:port and path?query the way the reparse did), so the pre-parsed header can be copy()'d into the state machine, skipping a full parse_req per request. In local benchmarking this roughly doubles small-request throughput (~800K → ~1.68M req/s).

Co-Author: Masakazu Kitajo

@zwoop zwoop added this to the 11.0.0 milestone Jul 23, 2026
@zwoop zwoop self-assigned this Jul 23, 2026
Copilot AI review requested due to automatic review settings July 23, 2026 02:36
@zwoop zwoop added the HTTP/2 label Jul 23, 2026

Copilot AI 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.

Pull request overview

This PR reduces HTTP/2 per-request overhead in ATS by avoiding avoidable allocations/copies during HPACK processing and by introducing a fast-path that hands a decoded request header directly to HttpSM (skipping serialize + parse). It also updates the HTTP/2→1.1 conversion to normalize URL components to preserve legacy cache/remap behavior, and adds a gold test to cover the new path.

Changes:

  • Decode contiguous HPACK header blocks in-place (avoids per-request malloc/memcpy/free) and encode HEADERS using an on-stack ts::LocalBuffer for typical sizes.
  • Add a fast-path handoff to HttpSM using a borrowed pre-parsed HTTPHdr, skipping HTTP/1.1 serialization and parse_req.
  • Normalize :authority and :path in the 2→1.1 converter (host:port split; query/fragment split; leading slash normalization) and add an AuTest replay to validate cache/remap parity.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/gold_tests/h2/replay/h2_request_handling.replay.yaml New replay validating URL normalization and cache-key parity for the HTTP/2 fast path.
tests/gold_tests/h2/h2_request_handling.test.py New gold test invoking the replay.
src/proxy/http2/Http2Stream.cc Adds fast-path pre-parsed request handoff and strict-uri compliance checks.
src/proxy/http2/Http2ConnectionState.cc In-place header-block decode when contiguous; stack-buffer HEADERS encoding via templated LocalBuffer.
src/proxy/http/HttpSM.cc Consumes an optional borrowed pre-parsed request header instead of parsing from an IO buffer.
src/proxy/hdrs/VersionConverter.cc Normalizes :authority and :path to match legacy serialize+reparse behavior (host/port + query/fragment splits).
src/proxy/hdrs/URL.cc Adds url_is_uri_compliant() helper used for strict-uri checks on the fast path.
include/proxy/http2/Http2Stream.h Adds decode_header_blocks() overload taking an explicit buffer pointer/length.
include/proxy/http/HttpSM.h Adds pre-parsed request setter/query API and backing member.
include/proxy/hdrs/URL.h Declares url_is_uri_compliant().
include/proxy/hdrs/HdrHeap.h Notes coupling between HdrHeap::DEFAULT_SIZE and the HTTP/2 on-stack encode buffer sizing.

Comment thread src/proxy/http/HttpSM.cc Outdated
Comment thread src/proxy/http2/Http2Stream.cc Outdated
Comment thread src/proxy/hdrs/URL.cc
Comment thread include/proxy/hdrs/URL.h
Comment thread include/proxy/http/HttpSM.h Outdated
Comment thread src/proxy/hdrs/VersionConverter.cc Outdated
Comment thread include/proxy/hdrs/HdrHeap.h Outdated
Comment thread src/proxy/http2/Http2ConnectionState.cc
Comment thread src/proxy/http2/Http2Stream.cc Outdated
Comment thread src/proxy/http2/Http2ConnectionState.cc
@JosiahWI
JosiahWI self-requested a review July 23, 2026 12:05
Comment thread src/proxy/http/HttpSM.cc Outdated
JosiahWI added a commit that referenced this pull request Jul 23, 2026
* Document HTTP methods for #13420 review

* Make changes requested by Brian Neradt

  Put brief sentence on opening line
  Use in/out/in,out parameter markers
  Clarify that `@` headers are also included in length
Copilot AI review requested due to automatic review settings July 24, 2026 19:45

Copilot AI 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.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.

Comment thread src/proxy/http2/Http2Stream.cc
@zwoop

zwoop commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

@JosiahWI Note that most of this got rewritten again, from suggestions from Masakazu. We'll have to rerun copilot etc.

@zwoop

zwoop commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

[approve ci autest]

Copilot AI 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.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.

Comment thread src/proxy/http/HttpSM.cc
Copilot AI review requested due to automatic review settings July 25, 2026 05:53

Copilot AI 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.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.

Comment thread src/proxy/hdrs/URL.cc
Copilot AI review requested due to automatic review settings July 25, 2026 06:03

Copilot AI 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.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated no new comments.

Copilot AI review requested due to automatic review settings July 25, 2026 06:17

Copilot AI 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.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated no new comments.

Copilot AI review requested due to automatic review settings July 25, 2026 16:10

Copilot AI 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.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated no new comments.

Copilot AI review requested due to automatic review settings July 26, 2026 16:46

Copilot AI 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.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.

Comment thread src/proxy/http2/Http2Stream.cc
@cmcfarlen
cmcfarlen requested a review from bryancall July 27, 2026 22:28
Comment thread include/proxy/http/HttpSM.h
Comment thread include/proxy/http2/Http2Stream.h Outdated
cmcfarlen pushed a commit to cmcfarlen/trafficserver that referenced this pull request Jul 29, 2026
* Document HTTP methods for apache#13420 review

* Make changes requested by Brian Neradt

  Put brief sentence on opening line
  Use in/out/in,out parameter markers
  Clarify that `@` headers are also included in length

(cherry picked from commit e241265)
zwoop added 3 commits July 30, 2026 12:21
Decode HPACK header blocks in place when the whole block is present and
contiguous in the frame reader, avoiding a per-request malloc + memcpy +
free. Encode HEADERS frames into a stack-backed LocalBuffer (up to
2 * HdrHeap::DEFAULT_SIZE) so typical response headers skip a heap
allocation, with a static_assert guarding the stack budget.
The H2 read path serialized each decoded request header so HttpSM could
reparse it. Instead, normalize the URL in the 2->1.1 converter (split
host:port and path?query, as a reparse would) and hand the decoded
header to HttpSM via a refcounted copy(), skipping serialize+reparse.
Roughly doubles small-request throughput.

The fast path requires a successful 2->1.1 conversion (so a malformed
request still gets its 400) and re-checks strict_uri_parsing on the
target; non-compliant requests fall back to the serialize path. Header
size stays bounded by the aggregate limits still in force here
(SETTINGS_MAX_HEADER_LIST_SIZE and request_header_max_size); parse_req's
per-field and request-line sub-limits are not separately applied.
Proxy Verifier replay coverage for the HTTP/2 <-> HttpSM fast-path
handoff in all three directions: inbound request URL normalization
(explicit-port and IPv6 :authority) and query-string / cross-protocol
cache-key parity; client response emission (bodyless 204/304/HEAD and
header preservation); and outbound server-request handling to an
HTTP/2 origin (GET with query, POST with body).

Extend http2_txn_start_read_gate with a bodyless GET. Its only
transaction was a POST, so a dropped read event still had DATA frames
to recover on, leaving the END_STREAM-on-HEADERS case uncovered.

Also fix http2 test case 8, which piped curl's stderr with the bash 4
"|&" operator. Autest runs commands through /bin/sh, which is bash 3.2
on macOS, so that run died with a syntax error before reaching ATS.
Copilot AI review requested due to automatic review settings July 31, 2026 01:35

Copilot AI 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.

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (4)

tests/gold_tests/h2/http2_fc_iso.test.py:74

  • Typo in docstring: "paramenter" should be "parameter".
        records.yaml file. If the paramenter is None, then no window size

tests/gold_tests/h2/http2_fc_iso.test.py:79

  • Typo in docstring: "paramenter" should be "parameter".
        records.yaml file. If the paramenter is None, then no policy

tests/gold_tests/h2/http2_fc_iso.test.py:132

  • New Python test code should prefer f-strings over str.format() (this is also more consistent with the rest of this file’s use of f'' strings).
                'proxy.config.ssl.server.private_key.path': f'{ts.Variables.SSLDir}',
                'proxy.config.ssl.client.verify.server.policy': 'PERMISSIVE',
                'proxy.config.dns.nameservers': '127.0.0.1:{0}'.format(self._dns.Variables.Port),
                'proxy.config.dns.resolv_conf': 'NULL',
                'proxy.config.http.insert_age_in_response': 0,

tests/gold_tests/h2/http2_fc_iso.test.py:69

  • Typo in docstring: "paramenter" should be "parameter".

This issue also appears in the following locations of the same file:

  • line 74
  • line 79
        records.yaml file. If the paramenter is None, then no window size

@zwoop

zwoop commented Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

[approve ci autest]

zwoop added 2 commits July 30, 2026 22:12
Replace the HttpSM::_pre_parsed_ua_request borrow pointer with virtual
supports_direct_header_passing(), is_parsed_receive_header_ready() and
parsed_receive_header() on ProxyTransaction, and use the same seam for
the response and outbound-request directions. All three skip the
serialize+reparse round-trip between HttpSM and the HTTP/2 stream.

Request in: HttpSM pulls the decoded header from the transaction in
state_read_client_request_header rather than the stream pushing a raw
pointer. The stream owns _receive_header and is torn down with the SM,
so the copy cannot see a dangling borrow; that drops the timeout
null-out and the synchronous-delivery assert.

Response out / request out: write_response_header_into_buffer and
setup_server_send_request hand client_response / server_request to the
stream instead of serializing them. update_write_request copies the
fields onto _send_header, preserving the pseudo-headers that
create(HTTP_2_0) installed and the 1.1->2 conversion fills -- a plain
copy() would wipe them. A bodyless message (204/304/HEAD, or a GET to
an H2 origin) has no body bytes to drive the write, so it is flushed
via has_pending_send_header(). The ready flag re-arms per header so 1xx
interim responses and retried requests each deliver.

Because the header no longer passes through a buffer, everything that
inferred it from one had to be corrected. client_response_hdr_bytes
stays 0 on this path, so reported_client_response_hdr_bytes() serves
the readers that mean "bytes the client received": logging,
TSHttpTxnClientRespHdrBytesGet(), the size stats, and the
tunnel_handler_post_ua guard that decides whether a response header has
already gone out -- left raw, an early origin response followed by a
post-body timeout would synthesize an error over it. Readers doing
tunnel byte arithmetic keep the raw counter. The tunnel also identifies
the UA consumer by comparing against get_ua_txn() rather than casting
on vc_type, which response plugin agents share while carrying an
INKVConnInternal.

The interface design is adopted from Masakazu Kitajo's
no-header-marshaling patch. The VersionConverter URL parity work and
the H2 allocation reductions on this branch are unchanged.
Restore the inbound parse_resp() fallback in update_write_request():
the direct-header fast path only covers final responses, so serialized
1xx responses (100-continue, 103 early-hints) written by
setup_100_continue_transfer() were dropped instead of being emitted as
HEADERS frames.

Gate the request fast path on the parse_req checks it would otherwise
skip, falling back to serialize+parse when any of them would reject:
the method must be all-token; a Content-Length must be a single
1*DIGIT with no differing duplicate and no Transfer-Encoding; and the
Host built from :authority must satisfy http_parse_host_header(), the
same test validate_hdr_host() applies. url_parse_internet() accepts the
userinfo that RFC 9113 8.3.1 bans, so ":authority: user@host" would
otherwise reach the origin instead of being rejected.

Keep the fast path from mis-mapping an oversized header set to 414
(REQUEST_URI_TOO_LONG); it now returns a generic 400.
Copilot AI review requested due to automatic review settings July 31, 2026 05:53

Copilot AI 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.

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/proxy/hdrs/URL.cc:1220

  • url_is_uri_compliant() can be called with empty URL components (e.g. empty query/fragment), which yields value.data() == nullptr. In strict modes, this passes null pointers into url_is_strictly_compliant() / url_is_mostly_compliant(), which currently iterate using i < end; relational pointer comparisons on null pointers are undefined behavior. Add an early return for empty values (or otherwise avoid invoking the strict checks when value.empty()).
url_is_uri_compliant(int strict_uri_parsing, std::string_view value)
{
  const char *start = value.data();
  const char *end   = start + value.length();

tests/gold_tests/h2/http2_fc_iso.test.py:80

  • Typo in the docstring: "paramenter" should be "parameter" (appears multiple times in this block).
        records.yaml file. If the paramenter is None, then no window size

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.

3 participants