Repository navigation
feat: add client-side max connection age for HTTP/2 - #1320
Conversation
|
The server-side equivalent (#1316) applies a jitter to the max connection age ( Without jitter, a fleet of clients that connected at roughly the same time (which is exactly the situation after a scale-out or rolling deployment) will all retire and reconnect at the same moment, causing a synchronized reconnect spike on the servers. With the retirement being break-before-make, it would also cause a correlated latency spike across the clients. It would be good to keep the client and server settings consistent in naming, semantics and validation (jitter |
|
Thanks @Rayan-and-beyond for picking this up — the change is nicely contained, and it's great to see docs and tests included. A few further concerns beyond the jitter one above: 1. The setting shouldn't go through
Could this be a proper field on 2. A buffered request can be dropped if upstream completes while retiring During retirement the replacement 3. Retirement is break-before-make, which should at least be documented Once retirement starts, new requests are backpressured until all in-flight streams finish or
Make-before-break (open the new connection, move dispatch to it, let the old one drain) would avoid both issues. If that's out of scope for this PR, please spell out the trade-off in 4. Smaller points
|
|
Thanks, these were good catches. Addressed in 30e52c4:
The buffered-request regression initially exposed one additional closed-inlet pull edge in the replacement connection; that is fixed too. Fresh validation is green: settings spec 3/3, full plaintext persistent-client spec 13/13 (including normal reconnect paths), TLS retirement 2/2, MiMa on Scala 2.13/3.3, scalafmt, docs, and |
|
Thanks for the quick update @Rayan-and-beyond, having a proper public setting with a Java API is much better. One suggestion on the Java duration conversions. The new getter/setter round-trip through milliseconds (copying the existing def getPersistentConnectionMaxAge: Duration = Duration.ofMillis(persistentConnectionMaxAge.toMillis)
def withPersistentConnectionMaxAge(maxAge: Duration): Http2ClientSettings =
self.withPersistentConnectionMaxAge(maxAge.toMillis.millis)This truncates sub-millisecond precision. Since
import scala.jdk.DurationConverters._
def getPersistentConnectionMaxAge: java.time.Duration = persistentConnectionMaxAge.toJava
def withPersistentConnectionMaxAge(maxAge: java.time.Duration): Http2ClientSettings =
self.withPersistentConnectionMaxAge(maxAge.toScala)If the setting ever needs to support The existing |
|
Good point. I switched the new Java max-age accessor/mutator to Validation after the change:
I left the disabled value as |
|
@Rayan-and-beyond I merged #1316 - would you be able to rebase this and try to maintain consistency with it? |
Motivation: Long-lived managed HTTP/2 clients can stay pinned to existing server instances. Modification: Add a configurable maximum age that retires managed persistent HTTP/2 connections after in-flight requests drain. Result: Requests arriving after retirement establish a fresh connection while in-flight requests complete normally. Tests: - sbt validatePullRequest - sbt http2-tests/test - sbt +http-core/mimaReportBinaryIssues - sbt scalafmtCheckAll scalafmtSbtCheck - sbt +headerCheckAll - sbt docs/paradox - sbt checkCodeStyle - git diff --check References: Refs apache#1319
Expose client max-age and jitter as public settings, add per-connection jitter, and preserve a buffered request when the request source completes during retirement. Document break-before-make behavior and extend retirement/reconnect coverage.
…che#1316 Motivation: apache#1316 merged with `infinite` as the disabled value for `max-connection-age`, a `Duration` setting type and `JavaDurationConverter` for the Java API. The client setting used `0s` and `FiniteDuration`. Modification: - `persistent-connection-max-age` defaults to `infinite`, is a `Duration`, and must be > 0 or `infinite`, as on the server - Java accessors use `JavaDurationConverter`, so `ChronoUnit.FOREVER.getDuration` round-trips to `Duration.Inf` - reference.conf, scaladoc and docs follow the server wording - the jitter scheduling matches `Http2Demux` - settings tests move to a new `Http2ClientSettingsSpec`, mirroring `Http2ServerSettingsSpec` - MiMa excludes are reduced to the abstract members that need them - pekko-style imports in the changed files Result: The client and server max connection age settings share naming conventions, defaults, validation and Java conversion behaviour. Tests: - sbt "http-core/testOnly ...Http2ClientSettingsSpec ...Http2CommonSettingsSpec ...Http2ServerSettingsSpec": 16 passed - sbt "http2-tests/testOnly ...Http2PersistentClient*": 26 passed - sbt "http-core/mimaReportBinaryIssues": clean - sbt scalafmtAll and headerCreateAll run on changed modules References: Refs apache#1319, apache#1316
5d2523c to
3c81498
Compare
|
I merged this to get into 2.0.0-M2-RC1. If there are any issues with this PR, it can still be reworked in a new PR. Milestone releases don't mean that the APIs and behaviours need to be maintained for backward compatibility. |
Motivation
Long-lived managed HTTP/2 clients can remain pinned to existing server instances, leaving newly added instances underused after a scale-out or rolling deployment.
Modification
Add two HTTP/2 client settings:
pekko.http.client.http2.persistent-connection-max-age(defaultinfinite, disabled)pekko.http.client.http2.persistent-connection-max-age-jitter(default0.1, i.e. +/-10%)Both are exposed through the Scala and Java
Http2ClientSettingsAPIs. Jitter semantics and validation match the server-side setting in #1316:>= 0,< 1, and0disables jitter.When a managed persistent connection reaches its jittered age, it stops accepting new requests and drains in-flight requests before disconnecting. Retirement is currently break-before-make: a buffered/new request is held until the old connection disconnects, then a replacement connection is established. If
completion-timeoutexpires, remaining in-flight requests on the retiring connection are terminated.The reconnect path preserves a buffered request even when the request source completes during retirement. The timer now tracks the current
Connectedstate directly rather than using a callback stored at stage level.Result
Operators can periodically refresh long-lived managed HTTP/2 client connections without synchronized reconnect spikes, while the current break-before-make latency/timeout trade-off is documented explicitly.
Tests
Fresh validation after review changes:
sbt "http-core/testOnly org.apache.pekko.http.scaladsl.settings.Http2ClientSettingsSpec"^T 9 passedHttp2PersistentClientPlaintextSpec^T 13 passed, including the normal failure/reconnect pathssbt "+http-core/mimaReportBinaryIssues"^T pass on Scala 2.13.18 and 3.3.8sbt scalafmtCheckAll scalafmtSbtCheck^T passsbt docs/paradox^T pass (only pre-existing duplicate-anchor warnings)git diff --check^T passReferences
Fixes #1319