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..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 @@ -151,17 +151,32 @@ 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 beforeCreation = InstanceCountingExtension.createCount.get() + 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 + before shouldEqual beforeCreation + 1 + 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..e389e1d22f 100644 --- a/akka-actor-typed/src/main/resources/reference.conf +++ b/akka-actor-typed/src/main/resources/reference.conf @@ -14,8 +14,14 @@ 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. +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/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 { 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})") } } -