Skip to content

Hard-enforce max_active_streams_in at HTTP/2 stream creation - #13386

Merged
maskit merged 1 commit into
apache:masterfrom
maskit:h2-zero-window-cap-master
Jul 16, 2026
Merged

maskit merged 1 commit into
apache:masterfrom
maskit:h2-zero-window-cap-master

Conversation

@maskit

@maskit maskit commented Jul 15, 2026

Copy link
Copy Markdown
Member

proxy.config.http2.max_active_streams_in has only adjusted the advertised SETTINGS_MAX_CONCURRENT_STREAMS, leaving the proxy unable to bound buffered response memory against clients that open streams faster than the advisory throttle reacts. It now also carries a finite default so the cap is no longer effectively unlimited.

The new knob proxy.config.http2.max_active_streams_policy_in selects enforcement. Value 0 keeps the advisory behavior that lowers advertised concurrency to proxy.config.http2.min_concurrent_streams_in. Value 1 refuses new inbound streams with REFUSED_STREAM once the process-wide active-stream count reaches the limit and leaves the advertised value untouched, so the min_concurrent_streams_in reduction that disrupts some clients no longer applies.

max_active_streams_in now defaults to 200000 rather than 0. Operators should size it to their available memory budget; set it to 0 to disable the cap.

Refusal happens after HPACK decoding so the dynamic table stays in sync with the client and the connection survives. proxy.process.http2.max_active_streams_exceeded_in counts each refusal.

proxy.config.http2.max_active_streams_in has only adjusted the advertised SETTINGS_MAX_CONCURRENT_STREAMS, leaving the proxy unable to bound buffered response memory against clients that open streams faster than the advisory throttle reacts. It now also carries a finite default so the cap is no longer effectively unlimited.

The new knob proxy.config.http2.max_active_streams_policy_in selects enforcement. Value 0 keeps the advisory behavior that lowers advertised concurrency to proxy.config.http2.min_concurrent_streams_in. Value 1 refuses new inbound streams with REFUSED_STREAM once the process-wide active-stream count reaches the limit and leaves the advertised value untouched, so the min_concurrent_streams_in reduction that disrupts some clients no longer applies.

max_active_streams_in now defaults to 200000 rather than 0. Operators should size it to their available memory budget; set it to 0 to disable the cap.

Refusal happens after HPACK decoding so the dynamic table stays in sync with the client and the connection survives. proxy.process.http2.max_active_streams_exceeded_in counts each refusal.
@maskit maskit self-assigned this Jul 15, 2026
Copilot AI review requested due to automatic review settings July 15, 2026 14:05
@maskit maskit added the HTTP/2 label Jul 15, 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 pull request hard-enforces the inbound HTTP/2 proxy.config.http2.max_active_streams_in limit at stream creation time (after HPACK decode), adding a new policy knob to choose between the existing advisory throttling behavior and hard refusal with REFUSED_STREAM. It also updates defaults, documentation, and adds gold tests to validate enforcement and HPACK dynamic-table synchronization across refused streams.

Changes:

  • Add proxy.config.http2.max_active_streams_policy_in to select advisory throttling (0) vs hard refusal (1) when the process-wide inbound active-stream cap is reached.
  • Change the default proxy.config.http2.max_active_streams_in to 200000 and update HTTP/2 code paths to refuse new inbound streams under the enforce policy while leaving advertised concurrency unchanged.
  • Add gold tests (replays + custom HTTP/2 client) to validate enforcement behavior and ensure HPACK stays in sync across refused streams.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/gold_tests/h2/replay/http2_max_active_streams_enforce.replay.yaml New origin replay for enforce-mode scenario (streams refused at proxy).
tests/gold_tests/h2/replay/http2_max_active_streams_advisory.replay.yaml New origin replay for advisory-mode scenario (all streams reach origin).
tests/gold_tests/h2/http2_max_active_streams.test.py New gold test covering policy behavior and HPACK sync expectations.
tests/gold_tests/h2/clients/h2_max_active_streams.py New custom HTTP/2 client used by the gold test to exercise stream refusal and HPACK dynamic table reuse.
src/records/RecordsConfig.cc Update defaults and add the new max_active_streams_policy_in records.yaml knob definition.
src/proxy/http2/Http2ConnectionState.cc Enforce the cap on inbound streams after header decode and bypass advisory throttling of advertised settings under enforce policy.
src/proxy/http2/HTTP2.cc Initialize/read the new config, update default for max_active_streams_in, and register a new metric counter.
include/proxy/http2/HTTP2.h Add the new config field and new stats counter to the HTTP/2 stats block.
doc/admin-guide/files/records.yaml.en.rst Document default change and add docs for the new enforcement policy knob.

Comment thread tests/gold_tests/h2/clients/h2_max_active_streams.py
Comment thread tests/gold_tests/h2/http2_max_active_streams.test.py
Comment thread src/proxy/http2/Http2ConnectionState.cc
@maskit
maskit merged commit 154d8b0 into apache:master Jul 16, 2026
15 checks passed
@github-project-automation github-project-automation Bot moved this to For v10.2.0 in ATS v10.2.x Jul 16, 2026
@maskit maskit added this to the 11.0.0 milestone Jul 16, 2026
@cmcfarlen cmcfarlen moved this from For v10.2.0 to Picked v10.2.0 in ATS v10.2.x Jul 29, 2026
@cmcfarlen cmcfarlen modified the milestones: 11.0.0, 10.2.0 Jul 29, 2026
@cmcfarlen

Copy link
Copy Markdown
Contributor

Cherry-picked to 10.2.x

cmcfarlen pushed a commit that referenced this pull request Jul 29, 2026
proxy.config.http2.max_active_streams_in has only adjusted the advertised SETTINGS_MAX_CONCURRENT_STREAMS, leaving the proxy unable to bound buffered response memory against clients that open streams faster than the advisory throttle reacts. It now also carries a finite default so the cap is no longer effectively unlimited.

The new knob proxy.config.http2.max_active_streams_policy_in selects enforcement. Value 0 keeps the advisory behavior that lowers advertised concurrency to proxy.config.http2.min_concurrent_streams_in. Value 1 refuses new inbound streams with REFUSED_STREAM once the process-wide active-stream count reaches the limit and leaves the advertised value untouched, so the min_concurrent_streams_in reduction that disrupts some clients no longer applies.

max_active_streams_in now defaults to 200000 rather than 0. Operators should size it to their available memory budget; set it to 0 to disable the cap.

Refusal happens after HPACK decoding so the dynamic table stays in sync with the client and the connection survives. proxy.process.http2.max_active_streams_exceeded_in counts each refusal.

(cherry picked from commit 154d8b0)
cmcfarlen added a commit to cmcfarlen/trafficserver that referenced this pull request Jul 29, 2026
http2_max_active_streams and tls_secret_update_default used the
ssl_multicert.yaml Disk API, which the 10.2.x autest harness does not
provide -- it registers ssl_multicert.config -- so both tests threw
AttributeError at load time. Convert them to the flat
ssl_multicert.config form already used by other tests on the branch.

For tls_secret_update_default this also updates the reload touch target
and the diags.log wait condition, which looked for "ssl_multicert.yaml
finished loading"; on 10.2.x the SSL loader logs the actual filename
(ssl_multicert.config), so the reload wait would otherwise never become
ready.

These tests were backported (via apache#13386 and apache#13342) without adapting
their config-file format to the release branch.
cmcfarlen added a commit that referenced this pull request Jul 29, 2026
http2_max_active_streams and tls_secret_update_default used the
ssl_multicert.yaml Disk API, which the 10.2.x autest harness does not
provide -- it registers ssl_multicert.config -- so both tests threw
AttributeError at load time. Convert them to the flat
ssl_multicert.config form already used by other tests on the branch.

For tls_secret_update_default this also updates the reload touch target
and the diags.log wait condition, which looked for "ssl_multicert.yaml
finished loading"; on 10.2.x the SSL loader logs the actual filename
(ssl_multicert.config), so the reload wait would otherwise never become
ready.

These tests were backported (via #13386 and #13342) without adapting
their config-file format to the release branch.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: Picked v10.2.0

Development

Successfully merging this pull request may close these issues.

4 participants