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.git


The following commit(s) were added to refs/heads/main by this push:
     new e2169744d9 fix: handle Artery uid collisions gracefully and make 
system uids more unique (#3652)
e2169744d9 is described below

commit e2169744d9a84351234565924b01ad3ec210ec54
Author: PJ Fanning <[email protected]>
AuthorDate: Fri Oct 23 16:13:48 2026 +0100

    fix: handle Artery uid collisions gracefully and make system uids more 
unique (#3652)
    
    * fix: handle Artery uid collision more gracefully
    
    Motivation:
    When two remote systems with the same ActorSystem uid connect to a node,
    AssociationRegistry.setUID threw a plain IllegalArgumentException from
    completeHandshake. That escaped the inbound handshake stage, failed the
    inbound stream and could disturb the healthy association that already
    owned the uid.
    
    Modification:
    - Introduce internal Association.UidCollisionException (NoStackTrace) and
      throw it from AssociationRegistry.setUID on collision.
    - ArteryTransport.completeHandshake logs a warning, quarantines the
      colliding peer uid and returns a failed Future instead of throwing.
    - InboundHandshake only replies with HandshakeRsp when completing the
      handshake succeeded, and keeps pulling either way.
    - ActorSystemImpl reads an internal, test-only `pekko.test-only-uid`
      setting (when present) so uid collisions can be tested. It is not in
      reference.conf and the uid is random when it is absent.
    - Add RemoteConnectionSpec tests for collisions when connecting to and
      from two systems with the same uid; give each test its own echo actor
      name.
    
    Result:
    A uid collision is rejected for the new peer only; the existing
    association keeps working in both directions. No wire format change, and
    classic remoting is unaffected.
    
    Tests:
    - Not run locally - left to CI; scalafmt applied
    
    References:
    None - robustness improvement for Artery handshake
    
    * fix: generate a more unique Artery system uid
    
    Motivation:
    The actor system uid was a plain random Long picked when the ActorSystem is
    created. With Artery, two systems ending up with the same uid make
    associations fail, so the uid should be harder to collide across nodes.
    
    Modification:
    - Add an internal `systemUid` to `ActorRefProvider` and `RemoteTransport`;
      `ExtendedActorSystem.uid` now delegates to the provider and throws an
      IllegalStateException if it is read before the provider is initialized.
    - `LocalActorRefProvider` and classic `Remoting` keep a random uid.
    - `ArteryTransport` generates the uid when it starts, mixing the bind host,
      port and several timestamps (one taken early in `RemoteActorRefProvider`).
      The internal `pekko.test-only-uid` setting moves here.
    - `Slf4jLogger` resolves the uid lazily and omits the `pekkoUid` MDC value
      until it is available.
    - MiMa excludes for the new abstract methods on the @DoNotInherit
      `ActorRefProvider` and the internal `RemoteTransport`.
    
    Result:
    Artery system uids are much less likely to collide. The uid is still a
    Long on the wire, so mixed-version clusters interoperate.
    
    Tests:
    - Not run locally - left to CI; scalafmt applied
    
    References:
    None - Artery uid uniqueness
    
    * test: assert the Artery uid collision warning and use probe timeouts
    
    Motivation:
    Review of #3652 noted that EventFilter[UidCollisionException]() does not
    assert a collision: it matches Error events with that exception and allows
    any number of them, while the collision is logged as a Warning without the
    exception. The collision tests also still used explicit 500 ms deadlines,
    which the Akka 2.9.0 follow-up (akka-core#32177) replaced with the 
configured
    probe timeout, and the adapted files kept their older Lightbend notices.
    
    Modification:
    - Subscribe a probe to Warning events and expect a "UID collision" warning
      after each collision step. Handshake retries can log the collision more 
than
      once, so the tests expect at least one.
    - Use the default probe timeout for the eight round trips in the two 
collision
      tests; the retry-polling tests keep their deadlines.
    - Update the Lightbend notices of the files adapted from Akka 2.9.0 to the
      2023 source notices.
    
    Result:
    Removing the collision warning makes both collision tests fail.
    
    Tests:
    sbt "remote/testOnly org.apache.pekko.remote.artery.RemoteConnectionSpec 
org.apache.pekko.remote.artery.SystemUidSpec"
    (9 passed; both collision tests fail with the warning removed)
    sbt "actor/headerCheckAll" "remote/headerCheckAll" "slf4j/headerCheckAll"
    
    References:
    https://github.com/apache/pekko/pull/3652
---
 .../2.0.x.backwards.excludes/system-uid.excludes   |  19 ++
 .../org/apache/pekko/actor/ActorRefProvider.scala  |  12 +-
 .../scala/org/apache/pekko/actor/ActorSystem.scala |  17 +-
 .../2.0.x.backwards.excludes/system-uid.excludes   |  19 ++
 .../pekko/remote/RemoteActorRefProvider.scala      |   9 +-
 .../org/apache/pekko/remote/RemoteTransport.scala  |   6 +-
 .../scala/org/apache/pekko/remote/Remoting.scala   |   4 +
 .../pekko/remote/artery/ArteryTransport.scala      |  44 ++++-
 .../apache/pekko/remote/artery/Association.scala   |   7 +-
 .../org/apache/pekko/remote/artery/Handshake.scala |  18 +-
 .../pekko/remote/artery/RemoteConnectionSpec.scala | 199 ++++++++++++++++++++-
 .../apache/pekko/remote/artery/SystemUidSpec.scala |  66 +++++++
 .../org/apache/pekko/event/slf4j/Slf4jLogger.scala |  20 ++-
 13 files changed, 410 insertions(+), 30 deletions(-)

diff --git 
a/actor/src/main/mima-filters/2.0.x.backwards.excludes/system-uid.excludes 
b/actor/src/main/mima-filters/2.0.x.backwards.excludes/system-uid.excludes
new file mode 100644
index 0000000000..ff6e0ba2a9
--- /dev/null
+++ b/actor/src/main/mima-filters/2.0.x.backwards.excludes/system-uid.excludes
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Additional method in ActorRefProvider, which is @DoNotInherit
+ProblemFilters.exclude[ReversedMissingMethodProblem]("org.apache.pekko.actor.ActorRefProvider.systemUid")
diff --git a/actor/src/main/scala/org/apache/pekko/actor/ActorRefProvider.scala 
b/actor/src/main/scala/org/apache/pekko/actor/ActorRefProvider.scala
index 8655e38bf8..eb836b0e10 100644
--- a/actor/src/main/scala/org/apache/pekko/actor/ActorRefProvider.scala
+++ b/actor/src/main/scala/org/apache/pekko/actor/ActorRefProvider.scala
@@ -8,11 +8,12 @@
  */
 
 /*
- * Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
+ * Copyright (C) 2009-2023 Lightbend Inc. <https://www.lightbend.com>
  */
 
 package org.apache.pekko.actor
 
+import java.util.concurrent.ThreadLocalRandom
 import java.util.concurrent.atomic.AtomicLong
 
 import scala.annotation.implicitNotFound
@@ -178,6 +179,12 @@ import pekko.util.OptionVal
   @InternalApi
   private[pekko] def addressString: String
 
+  /**
+   * INTERNAL API: the uid of the actor system, see `ExtendedActorSystem.uid`
+   */
+  @InternalApi
+  private[pekko] def systemUid: Long
+
 }
 
 /**
@@ -789,4 +796,7 @@ private[pekko] class LocalActorRefProvider private[pekko] (
         addr
     }
   }
+
+  override private[pekko] val systemUid: Long =
+    ThreadLocalRandom.current.nextLong()
 }
diff --git a/actor/src/main/scala/org/apache/pekko/actor/ActorSystem.scala 
b/actor/src/main/scala/org/apache/pekko/actor/ActorSystem.scala
index e88f364cbe..7857c2fdc6 100644
--- a/actor/src/main/scala/org/apache/pekko/actor/ActorSystem.scala
+++ b/actor/src/main/scala/org/apache/pekko/actor/ActorSystem.scala
@@ -8,7 +8,7 @@
  */
 
 /*
- * Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
+ * Copyright (C) 2009-2023 Lightbend Inc. <https://www.lightbend.com>
  */
 
 package org.apache.pekko.actor
@@ -834,8 +834,6 @@ private[pekko] class ActorSystemImpl(
     setup: ActorSystemSetup)
     extends ExtendedActorSystem {
 
-  val uid: Long = ThreadLocalRandom.current.nextLong()
-
   if (!name.matches("""^[a-zA-Z0-9][a-zA-Z0-9-_]*$"""))
     throw new IllegalArgumentException(
       "invalid ActorSystem name [" + name +
@@ -861,6 +859,19 @@ private[pekko] class ActorSystemImpl(
     new Settings(classLoader, config, name, setup)
   }
 
+  override def uid: Long = {
+    try {
+      provider.systemUid
+    } catch {
+      case NonFatal(exc) =>
+        // could be NPE in RemoteActorRefProvider if the transport is not 
initialized yet
+        throw new IllegalStateException(
+          "uid accessed before provider has been initialized. " +
+          "This is a bug, please report at 
https://github.com/apache/pekko/issues";,
+          exc)
+    }
+  }
+
   protected def uncaughtExceptionHandler: Thread.UncaughtExceptionHandler =
     new Thread.UncaughtExceptionHandler() {
       def uncaughtException(thread: Thread, cause: Throwable): Unit = {
diff --git 
a/remote/src/main/mima-filters/2.0.x.backwards.excludes/system-uid.excludes 
b/remote/src/main/mima-filters/2.0.x.backwards.excludes/system-uid.excludes
new file mode 100644
index 0000000000..8298a50725
--- /dev/null
+++ b/remote/src/main/mima-filters/2.0.x.backwards.excludes/system-uid.excludes
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Additional method in internal RemoteTransport
+ProblemFilters.exclude[ReversedMissingMethodProblem]("org.apache.pekko.remote.RemoteTransport.systemUid")
diff --git 
a/remote/src/main/scala/org/apache/pekko/remote/RemoteActorRefProvider.scala 
b/remote/src/main/scala/org/apache/pekko/remote/RemoteActorRefProvider.scala
index 098e9ac00e..d287424545 100644
--- a/remote/src/main/scala/org/apache/pekko/remote/RemoteActorRefProvider.scala
+++ b/remote/src/main/scala/org/apache/pekko/remote/RemoteActorRefProvider.scala
@@ -8,7 +8,7 @@
  */
 
 /*
- * Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
+ * Copyright (C) 2009-2023 Lightbend Inc. <https://www.lightbend.com>
  */
 
 package org.apache.pekko.remote
@@ -167,6 +167,10 @@ private[pekko] class RemoteActorRefProvider(
     extends ActorRefProvider {
   import RemoteActorRefProvider._
 
+  // One part of the system uid generation. Taking this early to allow for 
some execution randomness until later
+  // when actually generating the uid.
+  private[pekko] val systemUidTimestamp1: Long = System.nanoTime()
+
   val remoteSettings: RemoteSettings = new RemoteSettings(settings.config)
 
   private[pekko] final val hasClusterOrUseUnsafe =
@@ -698,6 +702,9 @@ private[pekko] class RemoteActorRefProvider(
         local.addressString
     }
   }
+
+  override private[pekko] def systemUid: Long =
+    transport.systemUid
 }
 
 private[pekko] trait RemoteRef extends ActorRefScope {
diff --git 
a/remote/src/main/scala/org/apache/pekko/remote/RemoteTransport.scala 
b/remote/src/main/scala/org/apache/pekko/remote/RemoteTransport.scala
index 2977c33466..aa547c71bb 100644
--- a/remote/src/main/scala/org/apache/pekko/remote/RemoteTransport.scala
+++ b/remote/src/main/scala/org/apache/pekko/remote/RemoteTransport.scala
@@ -8,7 +8,7 @@
  */
 
 /*
- * Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
+ * Copyright (C) 2009-2023 Lightbend Inc. <https://www.lightbend.com>
  */
 
 package org.apache.pekko.remote
@@ -110,4 +110,8 @@ private[pekko] abstract class RemoteTransport(val system: 
ExtendedActorSystem, v
   @InternalStableApi
   def quarantine(address: Address, uid: Option[Long], reason: String): Unit
 
+  /**
+   * The uid of the actor system, see `ExtendedActorSystem.uid`.
+   */
+  def systemUid: Long
 }
diff --git a/remote/src/main/scala/org/apache/pekko/remote/Remoting.scala 
b/remote/src/main/scala/org/apache/pekko/remote/Remoting.scala
index 76f4d6446e..b585a47f3e 100644
--- a/remote/src/main/scala/org/apache/pekko/remote/Remoting.scala
+++ b/remote/src/main/scala/org/apache/pekko/remote/Remoting.scala
@@ -16,6 +16,7 @@ package org.apache.pekko.remote
 import java.net.URLEncoder
 import java.nio.charset.StandardCharsets
 import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.ThreadLocalRandom
 import java.util.concurrent.TimeoutException
 
 import scala.annotation.nowarn
@@ -164,6 +165,9 @@ private[remote] class Remoting(_system: 
ExtendedActorSystem, _provider: RemoteAc
   override def localAddressForRemote(remote: Address): Address =
     Remoting.localAddressForRemote(transportMapping, remote)
 
+  // classic remoting only uses the lower 32 bits of the uid, see 
AddressUidExtension
+  override val systemUid: Long = ThreadLocalRandom.current.nextLong()
+
   val log: LoggingAdapter = Logging(system.eventStream, classOf[Remoting])
   val eventPublisher = new EventPublisher(system, log, 
RemoteLifecycleEventsLogLevel)
 
diff --git 
a/remote/src/main/scala/org/apache/pekko/remote/artery/ArteryTransport.scala 
b/remote/src/main/scala/org/apache/pekko/remote/artery/ArteryTransport.scala
index ed59f3b43d..cb4df8adf9 100644
--- a/remote/src/main/scala/org/apache/pekko/remote/artery/ArteryTransport.scala
+++ b/remote/src/main/scala/org/apache/pekko/remote/artery/ArteryTransport.scala
@@ -8,7 +8,7 @@
  */
 
 /*
- * Copyright (C) 2016-2022 Lightbend Inc. <https://www.lightbend.com>
+ * Copyright (C) 2016-2023 Lightbend Inc. <https://www.lightbend.com>
  */
 
 package org.apache.pekko.remote.artery
@@ -39,12 +39,12 @@ import pekko.event.Logging
 import pekko.event.MarkerLoggingAdapter
 import pekko.pattern.after
 import pekko.protobufv3.internal.CodedInputStream
-import pekko.remote.AddressUidExtension
 import pekko.remote.ContainerFormats
 import pekko.remote.RemoteActorRef
 import pekko.remote.RemoteActorRefProvider
 import pekko.remote.RemoteTransport
 import pekko.remote.UniqueAddress
+import pekko.remote.artery.Association.UidCollisionException
 import pekko.remote.artery.Decoder.InboundCompressionAccess
 import pekko.remote.artery.Encoder.OutboundCompressionAccess
 import pekko.remote.artery.InboundControlJunction.ControlMessageObserver
@@ -329,6 +329,11 @@ private[remote] abstract class ArteryTransport(_system: 
ExtendedActorSystem, _pr
   override def addresses: Set[Address] = _addresses
   override def localAddressForRemote(remote: Address): Address = defaultAddress
 
+  /**
+   * Must not be accessed before `start()`, will throw NullPointerException 
otherwise.
+   */
+  override def systemUid: Long = _localAddress.uid
+
   protected val killSwitch: SharedKillSwitch = 
KillSwitches.shared("transportKillSwitch")
 
   // keyed by the streamId
@@ -406,14 +411,20 @@ private[remote] abstract class ArteryTransport(_system: 
ExtendedActorSystem, _pr
 
     val (port, boundPort) = bindInboundStreams()
 
+    val uid =
+      if (provider.settings.config.hasPath("pekko.test-only-uid"))
+        provider.settings.config.getLong("pekko.test-only-uid") // to be able 
to test uid collisions
+      else
+        generateSystemUid(settings.Bind.Hostname, port, 
provider.systemUidTimestamp1)
+
     _localAddress = UniqueAddress(
       Address(provider.remoteSettings.ProtocolName, system.name, 
settings.Canonical.Hostname, port),
-      AddressUidExtension(system).longAddressUid)
+      uid)
     _addresses = Set(_localAddress.address)
 
     _bindAddress = UniqueAddress(
       Address(provider.remoteSettings.ProtocolName, system.name, 
settings.Bind.Hostname, boundPort),
-      AddressUidExtension(system).longAddressUid)
+      uid)
 
     flightRecorder.transportUniqueAddressSet(_localAddress)
 
@@ -774,7 +785,12 @@ private[remote] abstract class ArteryTransport(_system: 
ExtendedActorSystem, _pr
     try {
       associationRegistry.setUID(peer).completeHandshake(peer)
     } catch {
-      case ShuttingDown => Future.successful(Done) // silence it
+      case ShuttingDown               => Future.successful(Done) // silence it
+      case exc: UidCollisionException =>
+        log.warning(exc.getMessage)
+        // quarantine will not do much since handshake not completed, but for 
good measures
+        quarantine(peer.address, Some(peer.uid), exc.getMessage, harmless = 
false)
+        Future.failed(exc)
     }
   }
 
@@ -1103,4 +1119,22 @@ private[remote] object ArteryTransport {
       case _               => "message"
     }
 
+  /**
+   * Generates the system uid from the bind hostname and port in combination 
with timestamps,
+   * which makes collisions between systems less likely than with a plain 
random number.
+   */
+  def generateSystemUid(hostname: String, port: Int, timestamp1: Long): Long = 
{
+    var hash1 = 23
+    hash1 = 31 * hash1 + hostname.hashCode
+    hash1 = 31 * hash1 + java.lang.Long.hashCode(timestamp1)
+    hash1 = 31 * hash1 + java.lang.Long.hashCode(System.currentTimeMillis())
+
+    var hash2 = 23
+    hash2 = 31 * hash2 + port
+    hash2 = 31 * hash2 + java.lang.Long.hashCode(System.nanoTime())
+    hash2 = 31 * hash2 + java.lang.Long.hashCode(System.currentTimeMillis())
+
+    (hash1.toLong << 32) | (hash2 & 0xFFFFFFFFL)
+  }
+
 }
diff --git 
a/remote/src/main/scala/org/apache/pekko/remote/artery/Association.scala 
b/remote/src/main/scala/org/apache/pekko/remote/artery/Association.scala
index 19f6e3a025..62702ebfc3 100644
--- a/remote/src/main/scala/org/apache/pekko/remote/artery/Association.scala
+++ b/remote/src/main/scala/org/apache/pekko/remote/artery/Association.scala
@@ -8,7 +8,7 @@
  */
 
 /*
- * Copyright (C) 2016-2022 Lightbend Inc. <https://www.lightbend.com>
+ * Copyright (C) 2016-2023 Lightbend Inc. <https://www.lightbend.com>
  */
 
 package org.apache.pekko.remote.artery
@@ -50,6 +50,7 @@ import pekko.remote.RemoteLogMarker
 import pekko.remote.UniqueAddress
 import pekko.remote.artery.ArteryTransport.AeronTerminated
 import pekko.remote.artery.ArteryTransport.ShuttingDown
+import pekko.remote.artery.Association.UidCollisionException
 import pekko.remote.artery.Encoder.OutboundCompressionAccess
 import pekko.remote.artery.InboundControlJunction.ControlMessageSubject
 import pekko.remote.artery.OutboundControlJunction.OutboundControlIngress
@@ -130,6 +131,8 @@ private[remote] object Association {
       streamKillSwitch: OptionVal[SharedKillSwitch],
       completed: Future[Done],
       stopping: OptionVal[StopSignal])
+
+  final class UidCollisionException(message: String) extends 
IllegalArgumentException(message) with NoStackTrace
 }
 
 /**
@@ -1190,7 +1193,7 @@ private[remote] class 
AssociationRegistry(createAssociation: Address => Associat
           a
         else
           // make sure we don't overwrite same UID with different association
-          throw new IllegalArgumentException(s"UID collision old [$previous] 
new [$a]")
+          throw new UidCollisionException(s"UID collision old [$previous] new 
[$a]")
       case _ =>
         // update associationsByUid Map with the uid -> association
         val newMap = currentMap.updated(peer.uid, a)
diff --git 
a/remote/src/main/scala/org/apache/pekko/remote/artery/Handshake.scala 
b/remote/src/main/scala/org/apache/pekko/remote/artery/Handshake.scala
index 0c4c3c3deb..a338eb00db 100644
--- a/remote/src/main/scala/org/apache/pekko/remote/artery/Handshake.scala
+++ b/remote/src/main/scala/org/apache/pekko/remote/artery/Handshake.scala
@@ -8,7 +8,7 @@
  */
 
 /*
- * Copyright (C) 2016-2022 Lightbend Inc. <https://www.lightbend.com>
+ * Copyright (C) 2016-2023 Lightbend Inc. <https://www.lightbend.com>
  */
 
 package org.apache.pekko.remote.artery
@@ -258,7 +258,7 @@ private[remote] class InboundHandshake(inboundContext: 
InboundContext, inControl
                   // that the other system is alive.
                   
inboundContext.association(from.address).associationState.lastUsedTimestamp.set(System.nanoTime())
 
-                  after(inboundContext.completeHandshake(from)) { () =>
+                  after(inboundContext.completeHandshake(from)) { _ =>
                     pull(in)
                   }
                 case _ =>
@@ -281,8 +281,9 @@ private[remote] class InboundHandshake(inboundContext: 
InboundContext, inControl
 
       private def onHandshakeReq(from: UniqueAddress, to: Address): Unit = {
         if (to == inboundContext.localAddress.address) {
-          after(inboundContext.completeHandshake(from)) { () =>
-            inboundContext.sendControl(from.address, 
HandshakeRsp(inboundContext.localAddress))
+          after(inboundContext.completeHandshake(from)) { success =>
+            if (success)
+              inboundContext.sendControl(from.address, 
HandshakeRsp(inboundContext.localAddress))
             pull(in)
           }
         } else {
@@ -300,15 +301,16 @@ private[remote] class InboundHandshake(inboundContext: 
InboundContext, inControl
         }
       }
 
-      private def after(first: Future[Done])(thenInside: () => Unit): Unit = {
+      private def after(first: Future[Done])(thenInside: Boolean => Unit): 
Unit = {
         first.value match {
-          case Some(_) =>
+          case Some(result) =>
             // This in the normal case (all but the first). The future will be 
completed
             // because handshake was already completed. Note that we send 
those HandshakeReq
             // periodically.
-            thenInside()
+            thenInside(result.isSuccess)
           case None =>
-            first.onComplete(_ => 
runInStage.invoke(thenInside))(ExecutionContext.parasitic)
+            first.onComplete(result => runInStage.invoke(() => 
thenInside(result.isSuccess)))(
+              ExecutionContext.parasitic)
         }
 
       }
diff --git 
a/remote/src/test/scala/org/apache/pekko/remote/artery/RemoteConnectionSpec.scala
 
b/remote/src/test/scala/org/apache/pekko/remote/artery/RemoteConnectionSpec.scala
index 997bb106ca..aa46f16877 100644
--- 
a/remote/src/test/scala/org/apache/pekko/remote/artery/RemoteConnectionSpec.scala
+++ 
b/remote/src/test/scala/org/apache/pekko/remote/artery/RemoteConnectionSpec.scala
@@ -8,7 +8,7 @@
  */
 
 /*
- * Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
+ * Copyright (C) 2009-2023 Lightbend Inc. <https://www.lightbend.com>
  */
 
 package org.apache.pekko.remote.artery
@@ -17,6 +17,11 @@ import scala.concurrent.duration._
 
 import org.apache.pekko
 import pekko.actor.ActorSystem
+import pekko.actor.ExtendedActorSystem
+import pekko.event.Logging
+import pekko.remote.RARP
+import pekko.remote.artery.AssociationState.UidKnown
+import pekko.remote.artery.AssociationState.UidUnknown
 import pekko.testkit.{ EventFilter, ImplicitSender, TestActors, TestEvent, 
TestProbe }
 
 class RemoteConnectionSpec extends 
ArteryMultiNodeSpec("pekko.remote.retry-gate-closed-for = 5s") with 
ImplicitSender {
@@ -29,16 +34,195 @@ class RemoteConnectionSpec extends 
ArteryMultiNodeSpec("pekko.remote.retry-gate-
         EventFilter.warning(pattern = "received dead letter.*")))
   }
 
+  private def warningsProbe(system: ActorSystem): TestProbe = {
+    val probe = new TestProbe(system)
+    system.eventStream.subscribe(probe.ref, classOf[Logging.Warning])
+    probe
+  }
+
+  // handshake retries can log the collision more than once, so expect at 
least one warning
+  private def expectCollisionWarning(warnings: TestProbe): Unit =
+    warnings.fishForMessage(hint = "UID collision warning") {
+      case w: Logging.Warning => w.message.toString.startsWith("UID collision 
old [")
+    }
+
   "Remoting between systems" should {
 
+    "handle uid collision when connection TO two systems with same uid" in {
+      val localProbe = new TestProbe(localSystem)
+      val localPort = 
RARP(localSystem).provider.getDefaultAddress.getPort().get
+
+      val conflictUid = localSystem.asInstanceOf[ExtendedActorSystem].uid + 1
+      val echoName = "echoA"
+
+      val remotePort1 = freePort()
+      val remoteSystem1 =
+        newRemoteSystem(extraConfig = Some(s"""
+          pekko.test-only-uid = $conflictUid
+          pekko.remote.artery.canonical.port=$remotePort1
+          """))
+      val remote1Probe = new TestProbe(remoteSystem1)
+
+      val remotePort2 = freePort()
+      val remoteSystem2 =
+        newRemoteSystem(extraConfig = Some(s"""
+          // same uid as remoteSystem1
+          pekko.test-only-uid = $conflictUid
+          pekko.remote.artery.canonical.port=$remotePort2
+          """))
+      val remote2Probe = new TestProbe(remoteSystem2)
+
+      localProbe.expectNoMessage(2.seconds)
+      localSystem.actorOf(TestActors.echoActorProps, echoName)
+      remoteSystem1.actorOf(TestActors.echoActorProps, echoName)
+      remoteSystem2.actorOf(TestActors.echoActorProps, echoName)
+
+      val selectionFromLocalToRemote1 =
+        
localSystem.actorSelection(s"pekko://${remoteSystem1.name}@localhost:$remotePort1/user/$echoName")
+      val selectionFromLocalToRemote2 =
+        
localSystem.actorSelection(s"pekko://${remoteSystem2.name}@localhost:$remotePort2/user/$echoName")
+      val selectionFromRemote1ToLocal =
+        
remoteSystem1.actorSelection(s"pekko://${localSystem.name}@localhost:$localPort/user/$echoName")
+      val selectionFromRemote2ToLocal =
+        
remoteSystem2.actorSelection(s"pekko://${localSystem.name}@localhost:$localPort/user/$echoName")
+
+      selectionFromLocalToRemote1.tell("ping1a", localProbe.ref)
+      localProbe.expectMsg("ping1a")
+
+      selectionFromRemote1ToLocal.tell("ping1b", remote1Probe.ref)
+      remote1Probe.expectMsg("ping1b")
+
+      val collisionWarnings1 = warningsProbe(localSystem)
+      selectionFromLocalToRemote2.tell("ping2a", localProbe.ref)
+      // doesn't get through
+      localProbe.expectNoMessage()
+      expectCollisionWarning(collisionWarnings1)
+      RARP(localSystem).provider.transport
+        .asInstanceOf[ArteryTransport]
+        .association(RARP(remoteSystem2).provider.getDefaultAddress)
+        .associationState
+        .uniqueRemoteAddressState() shouldBe UidUnknown // handshake not 
completed
+
+      val collisionWarnings2 = warningsProbe(localSystem)
+      selectionFromRemote2ToLocal.tell("ping2b", remote2Probe.ref)
+      // doesn't get through in other direction
+      remote2Probe.expectNoMessage()
+      expectCollisionWarning(collisionWarnings2)
+
+      RARP(remoteSystem2).provider.transport
+        .asInstanceOf[ArteryTransport]
+        .association(RARP(localSystem).provider.getDefaultAddress)
+        .associationState
+        .uniqueRemoteAddressState() shouldBe UidKnown // handshake was 
completed in other direction
+
+      RARP(localSystem).provider.transport
+        .asInstanceOf[ArteryTransport]
+        .association(RARP(remoteSystem1).provider.getDefaultAddress)
+        .associationState
+        .uniqueRemoteAddressState() shouldBe UidKnown // still intact
+
+      // still works
+      selectionFromLocalToRemote1.tell("ping1a again", localProbe.ref)
+      localProbe.expectMsg("ping1a again")
+
+      // still works in other direction
+      selectionFromRemote1ToLocal.tell("ping1b again", remote1Probe.ref)
+      remote1Probe.expectMsg("ping1b again")
+    }
+
+    "handle uid collision when connection FROM two systems with same uid" in {
+      // same kind of test as above, but connection is first established from 
the other remote systems
+      val localProbe = new TestProbe(localSystem)
+      val localPort = 
RARP(localSystem).provider.getDefaultAddress.getPort().get
+
+      val conflictUid = localSystem.asInstanceOf[ExtendedActorSystem].uid + 2
+      val echoName = "echoB"
+
+      val remotePort1 = freePort()
+      val remoteSystem1 =
+        newRemoteSystem(extraConfig = Some(s"""
+          pekko.test-only-uid = $conflictUid
+          pekko.remote.artery.canonical.port=$remotePort1
+          """))
+      val remote1Probe = new TestProbe(remoteSystem1)
+
+      val remotePort2 = freePort()
+      val remoteSystem2 =
+        newRemoteSystem(extraConfig = Some(s"""
+          // same uid as remoteSystem1
+          pekko.test-only-uid = $conflictUid
+          pekko.remote.artery.canonical.port=$remotePort2
+          """))
+      val remote2Probe = new TestProbe(remoteSystem2)
+
+      localProbe.expectNoMessage(2.seconds)
+      localSystem.actorOf(TestActors.echoActorProps, echoName)
+      remoteSystem1.actorOf(TestActors.echoActorProps, echoName)
+      remoteSystem2.actorOf(TestActors.echoActorProps, echoName)
+
+      val selectionFromLocalToRemote1 =
+        
localSystem.actorSelection(s"pekko://${remoteSystem1.name}@localhost:$remotePort1/user/$echoName")
+      val selectionFromLocalToRemote2 =
+        
localSystem.actorSelection(s"pekko://${remoteSystem2.name}@localhost:$remotePort2/user/$echoName")
+      val selectionFromRemote1ToLocal =
+        
remoteSystem1.actorSelection(s"pekko://${localSystem.name}@localhost:$localPort/user/$echoName")
+      val selectionFromRemote2ToLocal =
+        
remoteSystem2.actorSelection(s"pekko://${localSystem.name}@localhost:$localPort/user/$echoName")
+
+      selectionFromRemote1ToLocal.tell("ping1b", remote1Probe.ref)
+      remote1Probe.expectMsg("ping1b")
+
+      selectionFromLocalToRemote1.tell("ping1a", localProbe.ref)
+      localProbe.expectMsg("ping1a")
+
+      val collisionWarnings3 = warningsProbe(localSystem)
+      selectionFromRemote2ToLocal.tell("ping2b", remote2Probe.ref)
+      // doesn't get through
+      localProbe.expectNoMessage()
+      expectCollisionWarning(collisionWarnings3)
+      RARP(localSystem).provider.transport
+        .asInstanceOf[ArteryTransport]
+        .association(RARP(remoteSystem2).provider.getDefaultAddress)
+        .associationState
+        .uniqueRemoteAddressState() shouldBe UidUnknown // handshake not 
completed
+
+      val collisionWarnings4 = warningsProbe(localSystem)
+      selectionFromLocalToRemote2.tell("ping2a", localProbe.ref)
+      // doesn't get through in other direction
+      remote2Probe.expectNoMessage()
+      expectCollisionWarning(collisionWarnings4)
+
+      RARP(remoteSystem2).provider.transport
+        .asInstanceOf[ArteryTransport]
+        .association(RARP(localSystem).provider.getDefaultAddress)
+        .associationState
+        .uniqueRemoteAddressState() shouldBe UidKnown // handshake was 
completed in other direction
+
+      RARP(localSystem).provider.transport
+        .asInstanceOf[ArteryTransport]
+        .association(RARP(remoteSystem1).provider.getDefaultAddress)
+        .associationState
+        .uniqueRemoteAddressState() shouldBe UidKnown // still intact
+
+      // still works
+      selectionFromRemote1ToLocal.tell("ping1b again", remote1Probe.ref)
+      remote1Probe.expectMsg("ping1b again")
+
+      // still works in other direction
+      selectionFromLocalToRemote1.tell("ping1a again", localProbe.ref)
+      localProbe.expectMsg("ping1a again")
+    }
+
     "be able to connect to system even if it's not there at first" in {
       muteSystem(localSystem)
       val localProbe = new TestProbe(localSystem)
+      val echoName = "echoC"
 
       val remotePort = freePort()
 
       // try to talk to it before it is up
-      val selection = 
localSystem.actorSelection(s"pekko://$nextGeneratedSystemName@localhost:$remotePort/user/echo")
+      val selection =
+        
localSystem.actorSelection(s"pekko://$nextGeneratedSystemName@localhost:$remotePort/user/$echoName")
       selection.tell("ping", localProbe.ref)
       localProbe.expectNoMessage(1.seconds)
 
@@ -47,7 +231,7 @@ class RemoteConnectionSpec extends 
ArteryMultiNodeSpec("pekko.remote.retry-gate-
 
       muteSystem(remoteSystem)
       localProbe.expectNoMessage(2.seconds)
-      remoteSystem.actorOf(TestActors.echoActorProps, "echo")
+      remoteSystem.actorOf(TestActors.echoActorProps, echoName)
 
       within(5.seconds) {
         awaitAssert {
@@ -59,17 +243,19 @@ class RemoteConnectionSpec extends 
ArteryMultiNodeSpec("pekko.remote.retry-gate-
 
     "allow other system to connect even if it's not there at first" in {
       val localSystem = newRemoteSystem()
+      val echoName = "echoD"
 
       val localPort = port(localSystem)
       muteSystem(localSystem)
 
       val localProbe = new TestProbe(localSystem)
-      localSystem.actorOf(TestActors.echoActorProps, "echo")
+      localSystem.actorOf(TestActors.echoActorProps, echoName)
 
       val remotePort = freePort()
 
       // try to talk to remote before it is up
-      val selection = 
localSystem.actorSelection(s"pekko://$nextGeneratedSystemName@localhost:$remotePort/user/echo")
+      val selection =
+        
localSystem.actorSelection(s"pekko://$nextGeneratedSystemName@localhost:$remotePort/user/$echoName")
       selection.tell("ping", localProbe.ref)
       localProbe.expectNoMessage(1.seconds)
 
@@ -80,7 +266,8 @@ class RemoteConnectionSpec extends 
ArteryMultiNodeSpec("pekko.remote.retry-gate-
       localProbe.expectNoMessage(2.seconds)
       val otherProbe = new TestProbe(remoteSystem)
       val otherSender = otherProbe.ref
-      val thisSelection = 
remoteSystem.actorSelection(s"pekko://${localSystem.name}@localhost:$localPort/user/echo")
+      val thisSelection =
+        
remoteSystem.actorSelection(s"pekko://${localSystem.name}@localhost:$localPort/user/$echoName")
       within(5.seconds) {
         awaitAssert {
           thisSelection.tell("ping", otherSender)
diff --git 
a/remote/src/test/scala/org/apache/pekko/remote/artery/SystemUidSpec.scala 
b/remote/src/test/scala/org/apache/pekko/remote/artery/SystemUidSpec.scala
new file mode 100644
index 0000000000..d8cbe1e061
--- /dev/null
+++ b/remote/src/test/scala/org/apache/pekko/remote/artery/SystemUidSpec.scala
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pekko.remote.artery
+
+import org.apache.pekko
+import pekko.actor.ActorSystem
+import pekko.actor.ExtendedActorSystem
+import pekko.remote.AddressUidExtension
+import pekko.remote.RARP
+import pekko.testkit.TestKit
+
+class SystemUidSpec extends ArteryMultiNodeSpec {
+
+  private def uidOf(sys: ActorSystem): Long = 
sys.asInstanceOf[ExtendedActorSystem].uid
+
+  "The system uid with Artery" must {
+
+    "be the uid of the transport local address" in {
+      val transport = 
RARP(localSystem).provider.transport.asInstanceOf[ArteryTransport]
+      uidOf(localSystem) should ===(transport.localAddress.uid)
+      uidOf(localSystem) should ===(transport.bindAddress.uid)
+      AddressUidExtension(localSystem).longAddressUid should 
===(uidOf(localSystem))
+    }
+
+    "be stable" in {
+      uidOf(localSystem) should ===(uidOf(localSystem))
+    }
+
+    "be different for different systems" in {
+      val otherSystem = newRemoteSystem()
+      uidOf(otherSystem) should !==(uidOf(localSystem))
+    }
+
+    "use the test-only-uid when defined" in {
+      val otherSystem = newRemoteSystem(extraConfig = 
Some("pekko.test-only-uid = 4711"))
+      uidOf(otherSystem) should ===(4711L)
+    }
+  }
+
+  "The system uid without remoting" must {
+
+    "be stable" in {
+      val localOnly = ActorSystem("SystemUidSpec-local")
+      try {
+        uidOf(localOnly) should ===(uidOf(localOnly))
+      } finally {
+        TestKit.shutdownActorSystem(localOnly)
+      }
+    }
+  }
+}
diff --git 
a/slf4j/src/main/scala/org/apache/pekko/event/slf4j/Slf4jLogger.scala 
b/slf4j/src/main/scala/org/apache/pekko/event/slf4j/Slf4jLogger.scala
index c14a078022..43227cbaee 100644
--- a/slf4j/src/main/scala/org/apache/pekko/event/slf4j/Slf4jLogger.scala
+++ b/slf4j/src/main/scala/org/apache/pekko/event/slf4j/Slf4jLogger.scala
@@ -8,7 +8,7 @@
  */
 
 /*
- * Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
+ * Copyright (C) 2009-2023 Lightbend Inc. <https://www.lightbend.com>
  */
 
 package org.apache.pekko.event.slf4j
@@ -75,7 +75,19 @@ class Slf4jLogger extends Actor with SLF4JLogging with 
RequiresMessageQueue[Logg
   val mdcPekkoUidAttributeName = "pekkoUid"
 
   private def pekkoAddress = 
context.system.asInstanceOf[ExtendedActorSystem].provider.addressString
-  private val pekkoUid: String = 
context.system.asInstanceOf[ExtendedActorSystem].uid.toString
+  // the uid is not available until the remote transport has been started, so 
it is resolved lazily
+  private var _pekkoUid: String = ""
+  private def pekkoUid: String = {
+    if (_pekkoUid.isEmpty) {
+      try {
+        _pekkoUid = 
context.system.asInstanceOf[ExtendedActorSystem].uid.toString
+      } catch {
+        case _: IllegalStateException =>
+        // when logging before the uid is generated (transport binding when 
remoting is enabled)
+      }
+    }
+    _pekkoUid
+  }
 
   def receive = {
 
@@ -135,7 +147,9 @@ class Slf4jLogger extends Actor with SLF4JLogging with 
RequiresMessageQueue[Logg
     MDC.put(mdcPekkoTimestamp, formatTimestamp(logEvent.timestamp))
     MDC.put(mdcActorSystemAttributeName, context.system.name)
     MDC.put(mdcPekkoAddressAttributeName, pekkoAddress)
-    MDC.put(mdcPekkoUidAttributeName, pekkoUid)
+    val uid = pekkoUid
+    if (uid.nonEmpty)
+      MDC.put(mdcPekkoUidAttributeName, uid)
     logEvent.mdc.foreach { case (k, v) => MDC.put(k, String.valueOf(v)) }
 
     try logStatement


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to