Skip to content

Fail fast when the scheme rules out HTTP/2 - #2334

Merged
hyperxpro merged 1 commit into
mainfrom
fix/h2-connection-waiter-gate
Sep 19, 2026
Merged

hyperxpro merged 1 commit into
mainfrom
fix/h2-connection-waiter-gate

Conversation

@hyperxpro

@hyperxpro hyperxpro commented Sep 19, 2026

Copy link
Copy Markdown
Member

Motivation:

A request refused a connection permit arms an Http2ConnectionWaiter that only a registered HTTP/2 connection can complete. On a cleartext origin with h2c disabled, no registration site can ever fire for that key, so the waiter can only expire: the request is held for connectTimeout and then fails with the permit exception it already had, ignoring the acquireFreeChannelTimeout the caller configured (0 by default: fail fast). A deferred request has no TimeoutsHolder either, so its own requestTimeout and absolute deadline go unenforced for the whole wait: a caller
asking for 200ms waits 5 seconds.

Any client talking to a cleartext origin with maxConnections set stalls on every over-cap request. Introduced in #2144 and carried through #2227, so present since 3.0.8, though only the non-blocking waiter shape in 3.0.12 and 3.0.13 is addressed
here; the earlier shapes blocked the caller thread and were fixed in 3.0.11.

Modification:

Return early, before arming the waiter, when the origin is not secured and cleartext HTTP/2 is disabled. With the default partitioning that is the exact complement of the sites that register an HTTP/2 connection: ALPN on a secured origin, h2c prior knowledge, and the post-CONNECT tunnel. The guard sits after pollHttp2, so a cleartext request that does find a live HTTP/2 channel still multiplexes; only the waiting is gated.

Result:

An over-cap request to such an origin fails immediately with TooManyConnectionsException instead of waiting out connectTimeout, and the failure is delivered on the caller's thread rather than on the client's shared timer
thread. NettyRequestThrottleTimeoutTest drops from ~33s to ~7s.

@hyperxpro hyperxpro changed the title Fail fast when the origin cannot carry HTTP/2 Fail fast when the scheme rules out HTTP/2 Sep 19, 2026
@hyperxpro
hyperxpro force-pushed the fix/h2-connection-waiter-gate branch from cadb948 to 5f7c0fc Compare September 19, 2026 20:16
@hyperxpro
hyperxpro merged commit 04013c6 into main Sep 19, 2026
17 checks passed
@hyperxpro
hyperxpro deleted the fix/h2-connection-waiter-gate branch September 19, 2026 20:28
hyperxpro added a commit that referenced this pull request Sep 20, 2026
Motivation:

Four follow-ups from #2333 and #2334.

1. A 3xx whose `Location` is missing or empty NPEs in `UriParser`
instead of reaching the handler.
2. A `ResponseFilter` that rebuilds the request drops the caller's
redirect refusal, so the hops past a replay are no longer covered.
3. An over-cap request to an `https` origin whose handshake settled on
HTTP/1.1 waits out `connectTimeout` — the half of #2334 left open.
4. `Uri.isSameBase` compares hosts with `String.equals`, so a redirect
differing only in host case reads as cross-origin and strips
`Authorization` and `Cookie`.

Modification:

1. Treat a missing or empty `Location` as not a redirect. RFC 9110
section 15.4 only redirects when one is provided, and an empty one
resolves back to the current URI.
2. Fold each hop's refusals into the exchange, monotonically: a rebuilt
request can tighten the posture but not relax it.
3. Mark a host HTTP/1.1 when ALPN settles there, fail anything parked on
it, and gate the waiter on that mark. Defer to a sibling that did
negotiate HTTP/2, since ALPN is per-connection.
4. Fold the host with `AsciiString.contentEqualsIgnoreCase`, the
comparison RFC 9110 section 4.2.3 asks for. A Unicode fold would make
`k.example` and a host spelled with U+212A the same base.

Result:

1. The 3xx is delivered as a response.
2. A refusal holds for the whole exchange, including hops a filter
retargets.
3. The request fails with the permit exception it already had, instead
of waiting out `connectTimeout`.
4. Credentials survive a case-differing same-origin redirect.
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.

1 participant