Skip to content

feat: add max-connection-age setting for HTTP/2 server connections (#1316) - #1317

Closed
Kreinoee wants to merge 1 commit into
apache:1.4.xfrom
Kreinoee:http2-server-max-connection-age-1.4.x
Closed

Kreinoee wants to merge 1 commit into
apache:1.4.xfrom
Kreinoee:http2-server-max-connection-age-1.4.x

Conversation

@Kreinoee

Copy link
Copy Markdown
Contributor

Motivation

Backport of #1316 to the 1.4.x line, so that the setting is usable before 2.0.0 ships.
See that PR for the full motivation: a max-connection-age for HTTP/2 server connections, the
server-side counterpart of grpc-java's maxConnectionAge (akka/akka-grpc#967 is the corresponding
request on the Akka side).

Modification

Cherry-pick of the single commit on #1316, with the branch-specific adaptations:

  • @since tags say 1.4.1 (this line) instead of 2.0.0 (main), following the precedent of
    max-header-list-size (http/2: bound incoming header blocks with max-header-list-size #1247).
  • The MiMa filter file lives in 1.4.x.backwards.excludes.
  • getMaxConnectionAge converts the possibly-infinite Scala duration inline
    (ChronoUnit.FOREVER.getDuration for infinite), because JavaDurationConverter does not exist on
    this branch.

Result

Same as #1316: operators can cap the lifetime of server-side HTTP/2 connections to
rebalance long-lived connections across server instances. Behavior is unchanged by default.

Verified against a real grpc-java (1.75.0) client, same setup as on #1316 but built from
this branch: with max-connection-age = 5s, a client issuing a unary call every 200 ms for 16 s
observed 0 failures across the connection retirements (the server saw 3 distinct client
connections), and a unary call that was in flight when the GOAWAY was sent completed normally on the
connection being drained.

Tests

  • sbt "http2-tests/test": pass, including the 2 new directional tests for max-connection-age in
    Http2ServerSpec
  • PR_TARGET_BRANCH=origin/1.4.x sbt validatePullRequest: all tasks pass except docs/Compile/paradox,
    which fails in this environment on a clean checkout of 1.4.x too (see the docs/paradox note below)
  • sbt "+http-core/mimaReportBinaryIssues": clean
  • sbt scalafmtCheckAll scalafmtSbtCheck: clean
  • sbt docs/paradox: environment failure unrelated to this change (fails identically on a clean
    checkout of 1.4.x with Error creating extended parser class ... org.pegdown.ParserWithDirectives)
  • manual end-to-end check against grpc-java 1.75.0 (see Result)

References

Backport of #1316. Refs akka/akka-grpc#967.

Motivation:
Long-lived HTTP/2 connections (as used by gRPC) lead to an uneven load
distribution across server instances: clients stay connected to the
instances they found at connect time, and instances added later (after
a scale-out or a rolling deploy) receive no share of the existing
traffic. The server is the side that can retire a connection
gracefully, via GOAWAY. grpc-java offers this as maxConnectionAge;
pekko-http has no equivalent (akka/akka-grpc#967 is the corresponding
request on the Akka side).

Modification:
Backport to 1.4.x of the main-branch change adding a
`pekko.http.server.http2.max-connection-age` setting, default
`infinite` (disabled). When a server connection reaches the configured
age, the existing graceful termination path is triggered:
GOAWAY(NO_ERROR) is sent, streams that are in flight complete normally,
streams opened after the GOAWAY are refused with
RST_STREAM(REFUSED_STREAM), and the connection is closed once no
streams remain. The age is jittered per connection by a configurable
fraction, `max-connection-age-jitter` (default 0.1 = +/- 10%, the value
grpc-java applies; 0 disables jitter), so that connections that were
opened together are not all closed at the same time.
`triggerTermination` now accepts an infinite deadline, in which case no
forced-close timer is scheduled. Also corrects the termination debug
log, which printed the timer key instead of the deadline.

Branch adaptations relative to main: `@since` tags say 1.4.1, the MiMa
filter file lives in 1.4.x.backwards.excludes, and getMaxConnectionAge
converts the possibly-infinite duration inline because
JavaDurationConverter does not exist on this branch.

Result:
Operators can cap the lifetime of server-side HTTP/2 connections to
rebalance long-lived connections across server instances. Behavior is
unchanged by default.

Tests:
- sbt "http2-tests/test": 344 tests pass, including 2 new directional
  tests for max-connection-age in Http2ServerSpec
- PR_TARGET_BRANCH=origin/1.4.x sbt validatePullRequest: all tasks pass
  except docs/Compile/paradox, which fails identically on a clean
  checkout of 1.4.x in this environment (pegdown/parboiled parser
  bootstrap error) - environment issue, not caused by this change
- sbt "+http-core/mimaReportBinaryIssues": clean, with 5 new
  ReversedMissingMethodProblem filters for the added methods
- sbt scalafmtCheckAll scalafmtSbtCheck: clean
- sbt headerCreateAll: no changes
- manual end-to-end check against grpc-java 1.75.0, built from this
  branch: with max-connection-age = 5s, a client calling every 200 ms
  for 16 s saw 0 failures across the connection retirements, and a
  unary call that was in flight when the GOAWAY was sent completed
  normally

References:
Refs akka/akka-grpc#967 - equivalent request against akka-http/akka-grpc.
1.4.x companion of the main-branch pull request adding the same setting.
@pjfanning

Copy link
Copy Markdown
Member

Let's wait till the first PR is reviewed. Please close this.

@Kreinoee
Kreinoee marked this pull request as ready for review September 24, 2026 18:04
@Kreinoee Kreinoee closed this Sep 24, 2026
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.

2 participants