This is an automated email from the ASF dual-hosted git repository.
pjfanning pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/pekko-http.git
The following commit(s) were added to refs/heads/main by this push:
new 9c63776a9 Follow-up to max-connection-age: infinite grace test, docs,
and FOREVER round-trip in Java setters (#1323)
9c63776a9 is described below
commit 9c63776a983bce89ea93a666b94f8e5d0cb42612
Author: PJ Fanning <[email protected]>
AuthorDate: Sat Oct 3 11:32:26 2026 +0100
Follow-up to max-connection-age: infinite grace test, docs, and FOREVER
round-trip in Java setters (#1323)
* Follow-up to max-connection-age review: document and test infinite grace
with a later terminate
Motivation:
Review of #1316 noted that reference.conf only documents one direction of
the interaction between max-connection-age and a server binding termination,
that the combination of max-connection-age-grace = infinite with a later
terminate was not tested, and that some new tests had tight timing margins.
Modification:
- reference.conf: state that a later terminate with an earlier deadline
shortens a drain started by the age, also with an infinite grace period,
mirroring http2.md.
- Http2ServerSpec: add a test for max-connection-age-grace = infinite
followed by terminate(10.millis).
- Http2ServerSpec: widen timing margins (age 1s vs expectNoBytes(700ms),
terminate(2s) vs expectNoBytes(700ms)).
Result:
The documented and tested behaviour covers both directions, and the timing
sensitive tests have more headroom on loaded CI machines.
Tests:
- sbt "http2-tests/testOnly ...Http2ServerSpec -- -z max-connection-age": 7
tests pass
- scalafmt on the changed spec
References:
Refs #1316
* Map ChronoUnit.FOREVER back to Duration.Inf in the other Java duration
setters
Motivation:
Getters such as ConnectionPoolSettings.getKeepAliveTimeout return
ChronoUnit.FOREVER.getDuration for an infinite value, but the matching Java
setters converted with toScala, which throws IllegalArgumentException for
that value. Passing a getter's result back to its setter failed on the
default settings of keep-alive-timeout, max-connection-lifetime and
periodic-keep-alive-max-idle.
Modification:
Use JavaDurationConverter.toScala, the inverse of the toJava used by the
getters, in the Java setters of ClientConnectionSettings.idleTimeout,
ConnectionPoolSettings idleTimeout, keepAliveTimeout, maxConnectionLifetime
and responseEntitySubscriptionTimeout, and
WebSocketSettings.periodicKeepAliveMaxIdle.
Result:
Infinite durations round-trip through the Java API.
Tests:
- sbt "http-core/testOnly org.apache.pekko.http.scaladsl.settings.*": 27
pass;
the 3 new round-trip tests fail without the fix
- sbt "http-core/mimaReportBinaryIssues": clean
References:
Refs #1316
---
http-core/src/main/resources/reference.conf | 3 +-
.../settings/ClientConnectionSettingsImpl.scala | 2 +-
.../impl/settings/ConnectionPoolSettingsImpl.scala | 8 ++---
.../http/javadsl/settings/WebSocketSettings.scala | 4 +--
.../settings/ClientConnectionSettingsSpec.scala | 22 +++++++++++++-
.../settings/ConnectionPoolSettingsSpec.scala | 35 ++++++++++++++++++++++
.../http/impl/engine/http2/Http2ServerSpec.scala | 31 ++++++++++++++++---
7 files changed, 92 insertions(+), 13 deletions(-)
diff --git a/http-core/src/main/resources/reference.conf
b/http-core/src/main/resources/reference.conf
index bea6d5582..e591cead0 100644
--- a/http-core/src/main/resources/reference.conf
+++ b/http-core/src/main/resources/reference.conf
@@ -366,7 +366,8 @@ pekko.http {
#
# If the connection is already being terminated when its age expires,
for example because the server
# binding is being terminated, the expiry has no effect and the
termination in progress keeps its own
- # deadline.
+ # deadline. Conversely, terminating the server binding with an earlier
deadline shortens a drain that the
+ # age started, also when `max-connection-age-grace` is `infinite`.
#
# The value `infinite` disables this mechanism and is the default.
max-connection-age = infinite
diff --git
a/http-core/src/main/scala/org/apache/pekko/http/impl/settings/ClientConnectionSettingsImpl.scala
b/http-core/src/main/scala/org/apache/pekko/http/impl/settings/ClientConnectionSettingsImpl.scala
index c5b13e8d8..a8e02f9b8 100644
---
a/http-core/src/main/scala/org/apache/pekko/http/impl/settings/ClientConnectionSettingsImpl.scala
+++
b/http-core/src/main/scala/org/apache/pekko/http/impl/settings/ClientConnectionSettingsImpl.scala
@@ -60,7 +60,7 @@ private[pekko] final case class ClientConnectionSettingsImpl(
withConnectingTimeout(newValue.toScala)
override def withIdleTimeout(newValue: java.time.Duration):
pekko.http.scaladsl.settings.ClientConnectionSettings =
- withIdleTimeout(newValue.toScala)
+ withIdleTimeout(JavaDurationConverter.toScala(newValue))
override def withStreamCancellationDelay(
newValue: java.time.Duration):
pekko.http.scaladsl.settings.ClientConnectionSettings =
diff --git
a/http-core/src/main/scala/org/apache/pekko/http/impl/settings/ConnectionPoolSettingsImpl.scala
b/http-core/src/main/scala/org/apache/pekko/http/impl/settings/ConnectionPoolSettingsImpl.scala
index f0dbf40d1..7aa72ba69 100644
---
a/http-core/src/main/scala/org/apache/pekko/http/impl/settings/ConnectionPoolSettingsImpl.scala
+++
b/http-core/src/main/scala/org/apache/pekko/http/impl/settings/ConnectionPoolSettingsImpl.scala
@@ -66,13 +66,13 @@ private[pekko] final case class ConnectionPoolSettingsImpl(
withMaxConnectionBackoff(newValue.toScala)
override def withIdleTimeout(newValue: java.time.Duration):
ConnectionPoolSettings =
- withIdleTimeout(newValue.toScala)
+ withIdleTimeout(JavaDurationConverter.toScala(newValue))
override def withKeepAliveTimeout(newValue: java.time.Duration):
ConnectionPoolSettings =
- withKeepAliveTimeout(newValue.toScala)
+ withKeepAliveTimeout(JavaDurationConverter.toScala(newValue))
override def withMaxConnectionLifetime(newValue: java.time.Duration):
ConnectionPoolSettings =
- withMaxConnectionLifetime(newValue.toScala)
+ withMaxConnectionLifetime(JavaDurationConverter.toScala(newValue))
def withUpdatedConnectionSettings(
f: ClientConnectionSettings => ClientConnectionSettings):
ConnectionPoolSettingsImpl =
@@ -80,7 +80,7 @@ private[pekko] final case class ConnectionPoolSettingsImpl(
hostOverrides = hostOverrides.map { case (k, v) => k ->
v.withUpdatedConnectionSettings(f) })
override def withResponseEntitySubscriptionTimeout(newValue:
java.time.Duration): ConnectionPoolSettings =
- withResponseEntitySubscriptionTimeout(newValue.toScala)
+
withResponseEntitySubscriptionTimeout(JavaDurationConverter.toScala(newValue))
/** INTERNAL API */
private[http] def copyDeep(
diff --git
a/http-core/src/main/scala/org/apache/pekko/http/javadsl/settings/WebSocketSettings.scala
b/http-core/src/main/scala/org/apache/pekko/http/javadsl/settings/WebSocketSettings.scala
index a8d90ef30..eabe96f42 100644
---
a/http-core/src/main/scala/org/apache/pekko/http/javadsl/settings/WebSocketSettings.scala
+++
b/http-core/src/main/scala/org/apache/pekko/http/javadsl/settings/WebSocketSettings.scala
@@ -21,11 +21,11 @@ import org.apache.pekko
import pekko.actor.ActorSystem
import pekko.annotation.DoNotInherit
import pekko.http.impl.settings.WebSocketSettingsImpl
+import pekko.http.impl.util.JavaDurationConverter
import pekko.util.ByteString
import com.typesafe.config.Config
import scala.concurrent.duration.Duration
-import scala.jdk.DurationConverters._
/**
* Public API but not intended for subclassing
@@ -60,7 +60,7 @@ trait WebSocketSettings { self: WebSocketSettingsImpl =>
* @since 1.3.0
*/
def withPeriodicKeepAliveMaxIdle(newValue: JDuration): WebSocketSettings =
- copy(periodicKeepAliveMaxIdle = newValue.toScala)
+ copy(periodicKeepAliveMaxIdle = JavaDurationConverter.toScala(newValue))
def withPeriodicKeepAliveData(newValue: Supplier[ByteString]):
WebSocketSettings =
copy(periodicKeepAliveData = () => newValue.get())
diff --git
a/http-core/src/test/scala/org/apache/pekko/http/scaladsl/settings/ClientConnectionSettingsSpec.scala
b/http-core/src/test/scala/org/apache/pekko/http/scaladsl/settings/ClientConnectionSettingsSpec.scala
index a6eda2193..147d04705 100644
---
a/http-core/src/test/scala/org/apache/pekko/http/scaladsl/settings/ClientConnectionSettingsSpec.scala
+++
b/http-core/src/test/scala/org/apache/pekko/http/scaladsl/settings/ClientConnectionSettingsSpec.scala
@@ -13,6 +13,26 @@
package org.apache.pekko.http.scaladsl.settings
+import java.time.temporal.ChronoUnit
+
+import scala.concurrent.duration._
+
import org.apache.pekko.testkit.PekkoSpec
-class ClientConnectionSettingsSpec extends PekkoSpec {}
+class ClientConnectionSettingsSpec extends PekkoSpec {
+ "ClientConnectionSettings" should {
+ "round-trip an infinite idle-timeout through the Java API" in {
+ val settings =
ClientConnectionSettings(system).withIdleTimeout(Duration.Inf)
+ settings.getIdleTimeout should ===(ChronoUnit.FOREVER.getDuration)
+ val roundTripped = settings.withIdleTimeout(settings.getIdleTimeout)
+ roundTripped.getIdleTimeout should ===(ChronoUnit.FOREVER.getDuration)
+ }
+
+ "round-trip an infinite websocket periodic-keep-alive-max-idle through the
Java API" in {
+ val settings =
ClientConnectionSettings(system).websocketSettings.withPeriodicKeepAliveMaxIdle(Duration.Inf)
+ settings.getPeriodicKeepAliveMaxIdle should
===(ChronoUnit.FOREVER.getDuration)
+ val roundTripped =
settings.withPeriodicKeepAliveMaxIdle(settings.getPeriodicKeepAliveMaxIdle)
+ roundTripped.getPeriodicKeepAliveMaxIdle should
===(ChronoUnit.FOREVER.getDuration)
+ }
+ }
+}
diff --git
a/http-core/src/test/scala/org/apache/pekko/http/scaladsl/settings/ConnectionPoolSettingsSpec.scala
b/http-core/src/test/scala/org/apache/pekko/http/scaladsl/settings/ConnectionPoolSettingsSpec.scala
index f82a0cb75..07b1e067e 100644
---
a/http-core/src/test/scala/org/apache/pekko/http/scaladsl/settings/ConnectionPoolSettingsSpec.scala
+++
b/http-core/src/test/scala/org/apache/pekko/http/scaladsl/settings/ConnectionPoolSettingsSpec.scala
@@ -13,6 +13,10 @@
package org.apache.pekko.http.scaladsl.settings
+import java.time.temporal.ChronoUnit
+
+import scala.concurrent.duration._
+
import org.apache.pekko
import pekko.testkit.PekkoSpec
import pekko.http.scaladsl.model.headers.`User-Agent`
@@ -160,6 +164,37 @@ class ConnectionPoolSettingsSpec extends PekkoSpec {
}
}
+ "ConnectionPoolSettings Java API" should {
+ "round-trip infinite durations" in {
+ val settings = ConnectionPoolSettings(system)
+ .withIdleTimeout(Duration.Inf)
+ .withKeepAliveTimeout(Duration.Inf)
+ .withMaxConnectionLifetime(Duration.Inf)
+ .withResponseEntitySubscriptionTimeout(Duration.Inf)
+ val forever = ChronoUnit.FOREVER.getDuration
+ settings.getIdleTimeout should ===(forever)
+ settings.getKeepAliveTimeout should ===(forever)
+ settings.getMaxConnectionLifetime should ===(forever)
+ settings.getResponseEntitySubscriptionTimeout should ===(forever)
+
+ val roundTripped = settings
+ .withIdleTimeout(settings.getIdleTimeout)
+ .withKeepAliveTimeout(settings.getKeepAliveTimeout)
+ .withMaxConnectionLifetime(settings.getMaxConnectionLifetime)
+
.withResponseEntitySubscriptionTimeout(settings.getResponseEntitySubscriptionTimeout)
+ roundTripped.getIdleTimeout should ===(forever)
+ roundTripped.getKeepAliveTimeout should ===(forever)
+ roundTripped.getMaxConnectionLifetime should ===(forever)
+ roundTripped.getResponseEntitySubscriptionTimeout should ===(forever)
+ }
+
+ "round-trip finite durations" in {
+ val settings = ConnectionPoolSettings(system).withIdleTimeout(42.seconds)
+ settings.getIdleTimeout should ===(java.time.Duration.ofSeconds(42))
+ settings.withIdleTimeout(settings.getIdleTimeout).getIdleTimeout should
===(java.time.Duration.ofSeconds(42))
+ }
+ }
+
def config(configString: String): ConnectionPoolSettings =
ConnectionPoolSettings(configString)
}
diff --git
a/http2-tests/src/test/scala/org/apache/pekko/http/impl/engine/http2/Http2ServerSpec.scala
b/http2-tests/src/test/scala/org/apache/pekko/http/impl/engine/http2/Http2ServerSpec.scala
index 2b6d19ab4..f7aea6ccf 100644
---
a/http2-tests/src/test/scala/org/apache/pekko/http/impl/engine/http2/Http2ServerSpec.scala
+++
b/http2-tests/src/test/scala/org/apache/pekko/http/impl/engine/http2/Http2ServerSpec.scala
@@ -2119,12 +2119,12 @@ class Http2ServerSpec extends
Http2SpecWithMaterializer("""
override def settings: ServerSettings = {
val default = super.settings
default.withHttp2Settings(
-
default.http2Settings.withMaxConnectionAge(500.millis).withMaxConnectionAgeJitter(0))
+
default.http2Settings.withMaxConnectionAge(1.second).withMaxConnectionAgeJitter(0))
}
- // with jitter disabled no GOAWAY is sent before the configured age:
checked for 400 of the 500 ms,
+ // with jitter disabled no GOAWAY is sent before the configured age:
checked for 700 of the 1000 ms,
// the rest is left as a margin for timer scheduling
- network.expectNoBytes(400.millis)
+ network.expectNoBytes(700.millis)
val (_, errorCode) = network.expectGOAWAY()
errorCode should ===(ErrorCode.NO_ERROR)
network.expectComplete()
@@ -2190,6 +2190,29 @@ class Http2ServerSpec extends
Http2SpecWithMaterializer("""
network.expectComplete()
terminated.futureValue
})
+ "close within the deadline of a later server binding termination while
draining with an infinite grace period".inAssertAllStagesStopped(
+ new TestSetup with RequestResponseProbes {
+ override def settings: ServerSettings = {
+ val default = super.settings
+ default.withHttp2Settings(
+
default.http2Settings.withMaxConnectionAge(500.millis).withMaxConnectionAgeJitter(0)
+ .withMaxConnectionAgeGrace(Duration.Inf))
+ }
+
+ network.sendRequest(1, HttpRequest())
+ user.expectRequest()
+
+ val (_, errorCode) = network.expectGOAWAY(1)
+ errorCode should ===(ErrorCode.NO_ERROR)
+
+ // with an infinite grace period no forced close is scheduled, the
connection waits for the request in flight
+ network.expectNoBytes(300.millis)
+
+ // so the deadline of a later termination always wins
+ val terminated = serverTerminator.terminate(10.millis)
+ network.expectComplete()
+ terminated.futureValue
+ })
"keep the remaining grace period when a later server binding termination
has a later deadline".inAssertAllStagesStopped(
new TestSetup with RequestResponseProbes {
override def settings: ServerSettings = {
@@ -2222,7 +2245,7 @@ class Http2ServerSpec extends
Http2SpecWithMaterializer("""
network.sendRequest(1, HttpRequest())
user.expectRequest()
- val terminated = serverTerminator.terminate(1.second)
+ val terminated = serverTerminator.terminate(2.seconds)
val (_, errorCode) = network.expectGOAWAY(1)
errorCode should ===(ErrorCode.NO_ERROR)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]