diff --git a/akka-actor-tests/src/test/scala/akka/serialization/SerializeSpec.scala b/akka-actor-tests/src/test/scala/akka/serialization/SerializeSpec.scala index 65191c77a1..20b129efe0 100644 --- a/akka-actor-tests/src/test/scala/akka/serialization/SerializeSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/serialization/SerializeSpec.scala @@ -532,6 +532,30 @@ class NoVerificationWarningOffSpec } } +class SerializerDeadlockSpec extends AkkaSpec { + + "SerializationExtension" must { + + "not be accessed from constructor of serializer" in { + intercept[IllegalStateException] { + val sys = ActorSystem( + "SerializerDeadlockSpec", + ConfigFactory.parseString(""" + akka { + actor { + creation-timeout = 1s + serializers { + test = "akka.serialization.DeadlockSerializer" + } + } + } + """)) + shutdown(sys) + }.getMessage should include("SerializationExtension from its constructor") + } + } +} + protected[akka] class NoopSerializer extends Serializer { def includeManifest: Boolean = false @@ -560,3 +584,17 @@ protected[akka] class NoopSerializer2 extends Serializer { protected[akka] final case class FakeThrowable(msg: String) extends Throwable(msg) with Serializable { override def fillInStackTrace = null } + +class DeadlockSerializer(system: ExtendedActorSystem) extends Serializer { + + // not allowed + SerializationExtension(system) + + def includeManifest: Boolean = false + + def identifier = 9999 + + def toBinary(o: AnyRef): Array[Byte] = Array.empty[Byte] + + def fromBinary(bytes: Array[Byte], clazz: Option[Class[_]]): AnyRef = null +} diff --git a/akka-actor/src/main/resources/reference.conf b/akka-actor/src/main/resources/reference.conf index 0496fd4a9c..51b60a2d10 100644 --- a/akka-actor/src/main/resources/reference.conf +++ b/akka-actor/src/main/resources/reference.conf @@ -119,7 +119,8 @@ akka { # In addition to the default there is akka.actor.StoppingSupervisorStrategy. guardian-supervisor-strategy = "akka.actor.DefaultSupervisorStrategy" - # Timeout for ActorSystem.actorOf + # Timeout for Extension creation and a few other potentially blocking + # initialization tasks. creation-timeout = 20s # Serializes and deserializes (non-primitive) messages to ensure immutability, @@ -140,6 +141,7 @@ akka { # CallingThreadDispatcher for a top-level actor. unstarted-push-timeout = 10s + # TypedActor deprecated since 2.6.0. typed { # Default timeout for the depredated TypedActor (not the new actor APIs in 2.6) methods with non-void return type timeout = 5s diff --git a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala index 5e296f9a20..390d5c7e5c 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala @@ -31,6 +31,7 @@ import scala.util.control.{ ControlThrowable, NonFatal } import scala.util.{ Failure, Success, Try } import akka.event.Logging.DefaultLogger +import akka.serialization.SerializationExtension object BootstrapSetup { @@ -1133,7 +1134,13 @@ private[akka] class ActorSystemImpl( private def findExtension[T <: Extension](ext: ExtensionId[T]): T = extensions.get(ext) match { case c: CountDownLatch => blocking { - c.await() + val awaitMillis = settings.CreationTimeout.duration.toMillis + if (!c.await(awaitMillis, TimeUnit.MILLISECONDS)) + throw new IllegalStateException( + s"Initialization of [$ext] took more than [$awaitMillis ms]. " + + (if (ext == SerializationExtension) + "A serializer must not access the SerializationExtension from its constructor. Use lazy init." + else "Could be deadlock due to cyclic initialization of extensions.")) } findExtension(ext) //Registration in process, await completion and retry case t: Throwable => throw t //Initialization failed, throw same again diff --git a/akka-docs/src/main/paradox/serialization.md b/akka-docs/src/main/paradox/serialization.md index 6e1fa47180..271165cde2 100644 --- a/akka-docs/src/main/paradox/serialization.md +++ b/akka-docs/src/main/paradox/serialization.md @@ -127,6 +127,10 @@ bytes to different objects. Then you only need to fill in the blanks, bind it to a name in your configuration and then list which classes that should be serialized using it. +The serializers are initialized eagerly by the `SerializationExtension` when the `ActorSystem` is started and +therefore a serializer itself must not access the `SerializationExtension` from its constructor. Instead, it +should access the `SerializationExtension` lazily. + ### Serializer with String Manifest diff --git a/akka-docs/src/test/java/jdocs/serialization/SerializationDocTest.java b/akka-docs/src/test/java/jdocs/serialization/SerializationDocTest.java index e16814d780..184ee2c5e8 100644 --- a/akka-docs/src/test/java/jdocs/serialization/SerializationDocTest.java +++ b/akka-docs/src/test/java/jdocs/serialization/SerializationDocTest.java @@ -196,8 +196,7 @@ public class SerializationDocTest { akka.actor.typed.ActorSystem.create(Behaviors.empty(), "example"); // Get the Serialization Extension - Serialization serialization = - SerializationExtension.get(akka.actor.typed.javadsl.Adapter.toClassic(system)); + Serialization serialization = SerializationExtension.get(system); // #programmatic-typed } } diff --git a/akka-docs/src/test/scala/docs/serialization/SerializationDocSpec.scala b/akka-docs/src/test/scala/docs/serialization/SerializationDocSpec.scala index 1b4994f267..bd122e62e6 100644 --- a/akka-docs/src/test/scala/docs/serialization/SerializationDocSpec.scala +++ b/akka-docs/src/test/scala/docs/serialization/SerializationDocSpec.scala @@ -214,12 +214,11 @@ package docs.serialization { def demonstrateTypedActorSystem(): Unit = { //#programmatic-typed import akka.actor.typed.ActorSystem - import akka.actor.typed.scaladsl.adapter._ val system = ActorSystem(Behaviors.empty, "example") // Get the Serialization Extension - val serialization = SerializationExtension(system.toClassic) + val serialization = SerializationExtension(system) //#programmatic-typed }