From 0c1ee2f4a802c833f02611951f173881bfde1a40 Mon Sep 17 00:00:00 2001 From: Johannes Rudolph Date: Tue, 23 Jan 2018 16:45:45 +0100 Subject: [PATCH 1/3] =typ initialize ActorRefResolver lazily in MiscMessageSerializer Serializers are initialized early on even before the underlying untyped ActorSystem is fully initialized. Accessing extensions might trigger a chain of initialization that might step on uninitialized internal bits later on. --- .../akka/actor/typed/internal/MiscMessageSerializer.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/akka-actor-typed/src/main/scala/akka/actor/typed/internal/MiscMessageSerializer.scala b/akka-actor-typed/src/main/scala/akka/actor/typed/internal/MiscMessageSerializer.scala index 2f1465a272..02e1a67100 100644 --- a/akka-actor-typed/src/main/scala/akka/actor/typed/internal/MiscMessageSerializer.scala +++ b/akka-actor-typed/src/main/scala/akka/actor/typed/internal/MiscMessageSerializer.scala @@ -14,7 +14,8 @@ import akka.serialization.{ BaseSerializer, SerializerWithStringManifest } @InternalApi class MiscMessageSerializer(val system: akka.actor.ExtendedActorSystem) extends SerializerWithStringManifest with BaseSerializer { - private val resolver = ActorRefResolver(system.toTyped) + // Serializers are initialized early on. `toTyped` might then try to initialize the untyped ActorSystemAdapter extension. + private lazy val resolver = ActorRefResolver(system.toTyped) private val ActorRefManifest = "a" def manifest(o: AnyRef): String = o match { From 79b5c79918dcf2740b76986e07d944799b78455e Mon Sep 17 00:00:00 2001 From: Johannes Rudolph Date: Wed, 24 Jan 2018 13:36:47 +0100 Subject: [PATCH 2/3] =typ load typed extensions as part of a separate untyped extension This is needed because the `ActorSystemAdapter` is instantiated early on in the typed ActorSystem initialization process already when the infrastructure is created to run the typed guardian. This is too early to initialize the typed extensions. The new untyped LoadTypedExtension is only loaded later on when the untyped ActorSystem is ready. --- .../akka/actor/typed/ExtensionsSpec.scala | 25 ++++++++++++++----- .../src/main/resources/reference.conf | 3 +++ .../internal/adapter/ActorSystemAdapter.scala | 24 +++++++++++++++--- 3 files changed, 42 insertions(+), 10 deletions(-) diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/ExtensionsSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/ExtensionsSpec.scala index d898b6356e..bf8094fd99 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/ExtensionsSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/ExtensionsSpec.scala @@ -151,17 +151,30 @@ class ExtensionsSpec extends TypedAkkaSpec { instance1 should be theSameInstanceAs instance2 } + "load registered typed extensions eagerly even for untyped system" in { + import akka.actor.typed.scaladsl.adapter._ + val untypedSystem = akka.actor.ActorSystem() + try { + val before = InstanceCountingExtension.createCount.get() + InstanceCountingExtension(untypedSystem.toTyped) + val after = InstanceCountingExtension.createCount.get() + + // should have been loaded even before it was accessed in the test because InstanceCountingExtension is listed + // as a typed library-extension in the config + after shouldEqual before + } finally { + untypedSystem.terminate().futureValue + } + } + "not create an extension multiple times when using the ActorSystemAdapter" in { import akka.actor.typed.scaladsl.adapter._ val untypedSystem = akka.actor.ActorSystem() try { + val ext1 = DummyExtension1(untypedSystem.toTyped) + val ext2 = DummyExtension1(untypedSystem.toTyped) - val before = InstanceCountingExtension.createCount.get() - InstanceCountingExtension(untypedSystem.toTyped) - val ext = InstanceCountingExtension(untypedSystem.toTyped) - val after = InstanceCountingExtension.createCount.get() - - (after - before) should ===(1) + ext1 should be theSameInstanceAs ext2 } finally { untypedSystem.terminate().futureValue diff --git a/akka-actor-typed/src/main/resources/reference.conf b/akka-actor-typed/src/main/resources/reference.conf index 5d2c93ef68..e32e523fae 100644 --- a/akka-actor-typed/src/main/resources/reference.conf +++ b/akka-actor-typed/src/main/resources/reference.conf @@ -16,6 +16,9 @@ akka.typed { library-extensions = ${?akka.typed.library-extensions} [] } +# Load typed extensions by an untyped unextension. +akka.library-extensions += "akka.actor.typed.internal.adapter.ActorSystemAdapter$LoadTypedExtensions" + akka.actor { serializers { typed-misc = "akka.actor.typed.internal.MiscMessageSerializer" diff --git a/akka-actor-typed/src/main/scala/akka/actor/typed/internal/adapter/ActorSystemAdapter.scala b/akka-actor-typed/src/main/scala/akka/actor/typed/internal/adapter/ActorSystemAdapter.scala index a62cced280..0a4bdade88 100644 --- a/akka-actor-typed/src/main/scala/akka/actor/typed/internal/adapter/ActorSystemAdapter.scala +++ b/akka-actor-typed/src/main/scala/akka/actor/typed/internal/adapter/ActorSystemAdapter.scala @@ -7,6 +7,8 @@ package adapter import java.util.concurrent.CompletionStage +import akka.actor +import akka.actor.ExtendedActorSystem import akka.actor.InvalidMessageException import akka.{ actor ⇒ a } @@ -29,8 +31,6 @@ import scala.compat.java8.FutureConverters @InternalApi private[akka] class ActorSystemAdapter[-T](val untyped: a.ActorSystemImpl) extends ActorSystem[T] with ActorRef[T] with internal.ActorRefImpl[T] with ExtensionsImpl { - loadExtensions() - import ActorRefAdapter.sendSystemMessage // Members declared in akka.actor.typed.ActorRef @@ -96,11 +96,28 @@ private[akka] object ActorSystemAdapter { object AdapterExtension extends a.ExtensionId[AdapterExtension] with a.ExtensionIdProvider { override def get(system: a.ActorSystem): AdapterExtension = super.get(system) - override def lookup = AdapterExtension + override def lookup() = AdapterExtension override def createExtension(system: a.ExtendedActorSystem): AdapterExtension = new AdapterExtension(system) } + /** + * An untyped extension to load configured typed extensions. It is loaded via + * akka.library-extensions. `loadExtensions` cannot be called from the AdapterExtension + * directly because the adapter is created too early during typed actor system creation. + * + * When on the classpath typed extensions will be loaded for untyped ActorSystems as well. + */ + class LoadTypedExtensions(system: a.ExtendedActorSystem) extends a.Extension { + ActorSystemAdapter.AdapterExtension(system).adapter.loadExtensions() + } + + object LoadTypedExtensions extends a.ExtensionId[LoadTypedExtensions] with a.ExtensionIdProvider { + override def lookup(): actor.ExtensionId[_ <: actor.Extension] = this + override def createExtension(system: ExtendedActorSystem): LoadTypedExtensions = + new LoadTypedExtensions(system) + } + def toUntyped[U](sys: ActorSystem[_]): a.ActorSystem = sys match { case adapter: ActorSystemAdapter[_] ⇒ adapter.untyped @@ -108,4 +125,3 @@ private[akka] object ActorSystemAdapter { s"($sys of class ${sys.getClass.getName})") } } - From 3ba4b2788964d43d72a34b04f22679bce66365f7 Mon Sep 17 00:00:00 2001 From: Johannes Rudolph Date: Mon, 22 Jan 2018 16:58:58 +0100 Subject: [PATCH 3/3] =typ #23697 start typed receptionist eagerly Fixes #23697 --- .../src/test/scala/akka/actor/typed/ExtensionsSpec.scala | 2 ++ akka-actor-typed/src/main/resources/reference.conf | 3 +++ 2 files changed, 5 insertions(+) diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/ExtensionsSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/ExtensionsSpec.scala index bf8094fd99..81cdfb0034 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/ExtensionsSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/ExtensionsSpec.scala @@ -153,6 +153,7 @@ class ExtensionsSpec extends TypedAkkaSpec { "load registered typed extensions eagerly even for untyped system" in { import akka.actor.typed.scaladsl.adapter._ + val beforeCreation = InstanceCountingExtension.createCount.get() val untypedSystem = akka.actor.ActorSystem() try { val before = InstanceCountingExtension.createCount.get() @@ -161,6 +162,7 @@ class ExtensionsSpec extends TypedAkkaSpec { // should have been loaded even before it was accessed in the test because InstanceCountingExtension is listed // as a typed library-extension in the config + before shouldEqual beforeCreation + 1 after shouldEqual before } finally { untypedSystem.terminate().futureValue diff --git a/akka-actor-typed/src/main/resources/reference.conf b/akka-actor-typed/src/main/resources/reference.conf index e32e523fae..e389e1d22f 100644 --- a/akka-actor-typed/src/main/resources/reference.conf +++ b/akka-actor-typed/src/main/resources/reference.conf @@ -14,6 +14,9 @@ akka.typed { # Should not be set by end user applications in 'application.conf', use the extensions property for that # library-extensions = ${?akka.typed.library-extensions} [] + + # Receptionist is started eagerly to allow clustered receptionist to gather remote registrations early on. + library-extensions += "akka.actor.typed.receptionist.Receptionist" } # Load typed extensions by an untyped unextension.