This is an automated email from the ASF dual-hosted git repository.
He-Pin pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/pekko.git
The following commit(s) were added to refs/heads/main by this push:
new 41752c0e30 test: make RemotingSpec bind retry match the classic Netty
BindException (#3551)
41752c0e30 is described below
commit 41752c0e304d5ab12536b2f9f6d3ba9d5b08d4af
Author: PJ Fanning <[email protected]>
AuthorDate: Fri Oct 9 08:38:59 2026 +0100
test: make RemotingSpec bind retry match the classic Netty BindException
(#3551)
Motivation:
`RemotingSpec` "allow other system to connect even if it's not there at
first" still fails intermittently in CI with `java.net.BindException: Address
already in use` when the port from `temporaryServerAddress()` is taken before
the new `ActorSystem` binds (seen again on #3549, `Check / Tests (scala213)`).
The retry in `selectionAndBind` only fires when the exception message
contains `"Failed to bind"`, but classic Netty remoting rethrows the raw
`BindException` from `Remoting.start`, so the guard never matched and the retry
added in #3015 was effectively dead code on this path. That string only exists
in the Artery TCP transport.
Modification:
- Add an `isBindFailure` helper that walks the cause chain and matches on
`java.net.BindException` (keeping the `"Failed to bind"` message check for
other transports), and use it as the retry guard in `selectionAndBind`.
- Add a directional test that starts an `ActorSystem` on a port already
held by a `ServerSocket` and asserts the thrown exception is recognized by
`isBindFailure` (and that an unrelated exception is not). The old string guard
returns `false` for this exception.
Result:
The lazy-connect tests retry on a fresh port when the temporary port is
already in use instead of failing the suite.
Tests:
- `scalafmt
remote/src/test/scala/org/apache/pekko/remote/classic/RemotingSpec.scala`
- `sbt "remote/testOnly org.apache.pekko.remote.classic.RemotingSpec"` — 23
tests, all passing (includes the new test)
- `git diff --check`
References:
Refs #1679, Refs #3015 - port race in RemotingSpec lazy-connect tests
---
.../apache/pekko/remote/classic/RemotingSpec.scala | 32 ++++++++++++++++++++--
1 file changed, 30 insertions(+), 2 deletions(-)
diff --git
a/remote/src/test/scala/org/apache/pekko/remote/classic/RemotingSpec.scala
b/remote/src/test/scala/org/apache/pekko/remote/classic/RemotingSpec.scala
index 8f451ece03..770c4e9507 100644
--- a/remote/src/test/scala/org/apache/pekko/remote/classic/RemotingSpec.scala
+++ b/remote/src/test/scala/org/apache/pekko/remote/classic/RemotingSpec.scala
@@ -14,9 +14,10 @@
package org.apache.pekko.remote.classic
import java.io.NotSerializableException
+import java.net.{ BindException, InetAddress, ServerSocket }
import java.util.concurrent.ThreadLocalRandom
-import scala.annotation.nowarn
+import scala.annotation.{ nowarn, tailrec }
import scala.concurrent.{ Await, Future }
import scala.concurrent.duration._
import scala.util.control.NonFatal
@@ -966,6 +967,17 @@ class RemotingSpec extends PekkoSpec(RemotingSpec.cfg)
with ImplicitSender with
}
+ // classic Netty rethrows the raw java.net.BindException from ActorSystem
startup, so look for it in
+ // the cause chain rather than relying on the message text
+ @tailrec
+ def isBindFailure(t: Throwable): Boolean =
+ t match {
+ case null
=> false
+ case _: BindException
=> true
+ case _ if t.getMessage != null && t.getMessage.contains("Failed to
bind") => true
+ case _
=> isBindFailure(t.getCause)
+ }
+
// retry a few times as the temporaryServerAddress can be taken by the
time the new actor system
// binds
def selectionAndBind(
@@ -984,13 +996,29 @@ class RemotingSpec extends PekkoSpec(RemotingSpec.cfg)
with ImplicitSender with
try {
(ActorSystem("other-system", otherConfig), otherSelection)
} catch {
- case NonFatal(ex) if ex.getMessage.contains("Failed to bind") &&
retries > 0 =>
+ case NonFatal(ex) if isBindFailure(ex) && retries > 0 =>
selectionAndBind(config, thisSystem, probe, retries = retries - 1)
case other =>
throw other
}
}
+ "recognize a bind failure when the configured port is already taken" in {
+ val taken = new ServerSocket(0, 1, InetAddress.getByName("localhost"))
+ try {
+ val config = ConfigFactory.parseString(s"""
+ pekko.remote.classic.enabled-transports =
["pekko.remote.classic.netty.tcp"]
+ pekko.remote.classic.netty.tcp.port = ${taken.getLocalPort}
+ """).withFallback(remoteSystem.settings.config)
+ val ex = intercept[Exception] {
+ val sys = ActorSystem("taken-port-system", config)
+ shutdown(sys)
+ }
+ isBindFailure(ex) shouldBe true
+ isBindFailure(new IllegalStateException("something else")) shouldBe
false
+ } finally taken.close()
+ }
+
"be able to connect to system even if it's not there at first" in {
val config = ConfigFactory.parseString(s"""
pekko.remote.classic.enabled-transports =
["pekko.remote.classic.netty.tcp"]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]