diff --git a/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/SpawnProtocolDocTest.java b/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/SpawnProtocolDocTest.java index 0ea545f1e0..3ae900d771 100644 --- a/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/SpawnProtocolDocTest.java +++ b/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/SpawnProtocolDocTest.java @@ -31,20 +31,21 @@ public class SpawnProtocolDocTest { public abstract static class HelloWorldMain { private HelloWorldMain() {} - public static final Behavior main = + public static final Behavior main = Behaviors.setup( context -> { // Start initial tasks // context.spawn(...) - return SpawnProtocol.behavior(); + return SpawnProtocol.create(); }); } // #main public static void main(String[] args) throws Exception { // #system-spawn - final ActorSystem system = ActorSystem.create(HelloWorldMain.main, "hello"); + final ActorSystem system = + ActorSystem.create(HelloWorldMain.main, "hello"); final Duration timeout = Duration.ofSeconds(3); CompletionStage> greeter = diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/SpawnProtocolSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/SpawnProtocolSpec.scala index 40b664baac..92e53d1ebc 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/SpawnProtocolSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/SpawnProtocolSpec.scala @@ -11,6 +11,8 @@ import akka.actor.typed.scaladsl.Behaviors import akka.util.Timeout import org.scalatest.{ Matchers, WordSpec, WordSpecLike } +import scala.concurrent.Future + object SpawnProtocolSpec { sealed trait Message final case class Ping(replyTo: ActorRef[Pong.type]) extends Message @@ -32,7 +34,7 @@ class SpawnProtocolSpec extends ScalaTestWithActorTestKit with WordSpecLike { "Spawn behavior" must { "spawn child actor" in { val parentReply = TestProbe[ActorRef[Message]]() - val parent = spawn(SpawnProtocol.behavior, "parent") + val parent = spawn(SpawnProtocol(), "parent") parent ! SpawnProtocol.Spawn(target, "child", Props.empty, parentReply.ref) val child = parentReply.receiveMessage() child.path.name should ===("child") @@ -43,17 +45,18 @@ class SpawnProtocolSpec extends ScalaTestWithActorTestKit with WordSpecLike { } "have nice API for ask" in { - val parent = spawn(SpawnProtocol.behavior, "parent2") + val parent = spawn(SpawnProtocol(), "parent2") import akka.actor.typed.scaladsl.AskPattern._ implicit val timeout = Timeout(5.seconds) - val parentReply = parent.ask(SpawnProtocol.Spawn(target, "child", Props.empty)) + val parentReply: Future[ActorRef[Ping]] = + parent.ask(SpawnProtocol.Spawn(target, "child", Props.empty, _)) val child = parentReply.futureValue val childReply = TestProbe[Pong.type]() child ! Ping(childReply.ref) } "be possible to use as guardian behavior" in { - val sys = ActorSystem(SpawnProtocol.behavior, "SpawnProtocolSpec2") + val sys = ActorSystem(SpawnProtocol(), "SpawnProtocolSpec2") try { val guardianReply = TestProbe[ActorRef[Message]]()(sys) sys ! SpawnProtocol.Spawn(target, "child1", Props.empty, guardianReply.ref) @@ -70,7 +73,7 @@ class SpawnProtocolSpec extends ScalaTestWithActorTestKit with WordSpecLike { "spawn with unique name when given name is taken" in { val parentReply = TestProbe[ActorRef[Message]]() - val parent = spawn(SpawnProtocol.behavior, "parent3") + val parent = spawn(SpawnProtocol(), "parent3") parent ! SpawnProtocol.Spawn(target, "child", Props.empty, parentReply.ref) val child0 = parentReply.receiveMessage() @@ -101,7 +104,7 @@ class StubbedSpawnProtocolSpec extends WordSpec with Matchers { "spawn with given name" in { val parentReply = TestInbox[ActorRef[Message]]() - val testkit = BehaviorTestKit(SpawnProtocol.behavior) + val testkit = BehaviorTestKit(SpawnProtocol()) testkit.run(SpawnProtocol.Spawn(target, "child", Props.empty, parentReply.ref)) val child = parentReply.receiveMessage() child.path.name should ===("child") @@ -110,7 +113,7 @@ class StubbedSpawnProtocolSpec extends WordSpec with Matchers { "spawn anonymous when name undefined" in { val parentReply = TestInbox[ActorRef[Message]]() - val testkit = BehaviorTestKit(SpawnProtocol.behavior) + val testkit = BehaviorTestKit(SpawnProtocol()) testkit.run(SpawnProtocol.Spawn(target, "", Props.empty, parentReply.ref)) val child = parentReply.receiveMessage() child.path.name should startWith("$") diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/DispatcherSelectorSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/DispatcherSelectorSpec.scala index da7cf5ae30..fe2124e7eb 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/DispatcherSelectorSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/DispatcherSelectorSpec.scala @@ -55,7 +55,7 @@ class DispatcherSelectorSpec extends ScalaTestWithActorTestKit(DispatcherSelecto } "select same dispatcher as parent" in { - val parent = spawn(SpawnProtocol.behavior, Props.empty.withDispatcherFromConfig("ping-pong-dispatcher")) + val parent = spawn(SpawnProtocol(), Props.empty.withDispatcherFromConfig("ping-pong-dispatcher")) val childProbe = createTestProbe[ActorRef[Ping]]() parent ! SpawnProtocol.Spawn(PingPong(), "child", Props.empty.withDispatcherSameAsParent, childProbe.ref) @@ -68,10 +68,10 @@ class DispatcherSelectorSpec extends ScalaTestWithActorTestKit(DispatcherSelecto } "select same dispatcher as parent, several levels" in { - val grandParent = spawn(SpawnProtocol.behavior, Props.empty.withDispatcherFromConfig("ping-pong-dispatcher")) + val grandParent = spawn(SpawnProtocol(), Props.empty.withDispatcherFromConfig("ping-pong-dispatcher")) val parentProbe = createTestProbe[ActorRef[SpawnProtocol.Spawn[Ping]]]() grandParent ! SpawnProtocol.Spawn( - SpawnProtocol.behavior, + SpawnProtocol(), "parent", Props.empty.withDispatcherSameAsParent, parentProbe.ref) diff --git a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/DispatchersDocSpec.scala b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/DispatchersDocSpec.scala index 770040decc..f768fc7665 100644 --- a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/DispatchersDocSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/DispatchersDocSpec.scala @@ -15,6 +15,8 @@ import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit import com.typesafe.config.ConfigFactory import org.scalatest.WordSpecLike +import scala.concurrent.Future + object DispatchersDocSpec { val config = ConfigFactory.parseString(""" @@ -60,19 +62,22 @@ class DispatchersDocSpec extends ScalaTestWithActorTestKit(DispatchersDocSpec.co "Actor Dispatchers" should { "support default and blocking dispatcher" in { val probe = TestProbe[Dispatcher]() - val actor: ActorRef[SpawnProtocol] = spawn(SpawnProtocol.behavior) + val actor: ActorRef[SpawnProtocol.Command] = spawn(SpawnProtocol()) - val withDefault = actor.ask(Spawn(giveMeYourDispatcher, "default", Props.empty)).futureValue - withDefault ! WhichDispatcher(probe.ref) + val withDefault: Future[ActorRef[WhichDispatcher]] = + actor.ask(Spawn(giveMeYourDispatcher, "default", Props.empty, _)) + withDefault.futureValue ! WhichDispatcher(probe.ref) probe.receiveMessage().id shouldEqual "akka.actor.default-dispatcher" - val withBlocking = actor.ask(Spawn(giveMeYourDispatcher, "default", DispatcherSelector.blocking())).futureValue - withBlocking ! WhichDispatcher(probe.ref) + val withBlocking: Future[ActorRef[WhichDispatcher]] = + actor.ask(Spawn(giveMeYourDispatcher, "default", DispatcherSelector.blocking(), _)) + withBlocking.futureValue ! WhichDispatcher(probe.ref) probe.receiveMessage().id shouldEqual "akka.actor.default-blocking-io-dispatcher" - val withCustom = - actor.ask(Spawn(giveMeYourDispatcher, "default", DispatcherSelector.fromConfig("your-dispatcher"))).futureValue - withCustom ! WhichDispatcher(probe.ref) + val withCustom: Future[ActorRef[WhichDispatcher]] = + actor.ask(Spawn(giveMeYourDispatcher, "default", DispatcherSelector.fromConfig("your-dispatcher"), _)) + + withCustom.futureValue ! WhichDispatcher(probe.ref) probe.receiveMessage().id shouldEqual "your-dispatcher" } } diff --git a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/SpawnProtocolDocSpec.scala b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/SpawnProtocolDocSpec.scala index 0531ab45bc..bb5cd8ad8e 100644 --- a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/SpawnProtocolDocSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/SpawnProtocolDocSpec.scala @@ -32,12 +32,12 @@ object SpawnProtocolDocSpec { //#main object HelloWorldMain { - val main: Behavior[SpawnProtocol] = + val main: Behavior[SpawnProtocol.Command] = Behaviors.setup { context => // Start initial tasks // context.spawn(...) - SpawnProtocol.behavior + SpawnProtocol() } } //#main @@ -51,7 +51,7 @@ class SpawnProtocolDocSpec extends ScalaTestWithActorTestKit with WordSpecLike { "be able to spawn actors" in { //#system-spawn - val system: ActorSystem[SpawnProtocol] = + val system: ActorSystem[SpawnProtocol.Command] = ActorSystem(HelloWorldMain.main, "hello") // needed in implicit scope for ask (?) @@ -61,7 +61,7 @@ class SpawnProtocolDocSpec extends ScalaTestWithActorTestKit with WordSpecLike { implicit val scheduler: Scheduler = system.scheduler val greeter: Future[ActorRef[HelloWorld.Greet]] = - system.ask(SpawnProtocol.Spawn(behavior = HelloWorld(), name = "greeter", props = Props.empty)) + system.ask(SpawnProtocol.Spawn(behavior = HelloWorld(), name = "greeter", props = Props.empty, _)) val greetedBehavior = Behaviors.receive[HelloWorld.Greeted] { (context, message) => context.log.info("Greeting for {} from {}", message.whom, message.from) @@ -69,7 +69,7 @@ class SpawnProtocolDocSpec extends ScalaTestWithActorTestKit with WordSpecLike { } val greetedReplyTo: Future[ActorRef[HelloWorld.Greeted]] = - system.ask(SpawnProtocol.Spawn(greetedBehavior, name = "", props = Props.empty)) + system.ask(SpawnProtocol.Spawn(greetedBehavior, name = "", props = Props.empty, _)) for (greeterRef <- greeter; replyToRef <- greetedReplyTo) { greeterRef ! HelloWorld.Greet("Akka", replyToRef) diff --git a/akka-actor-typed/src/main/scala/akka/actor/typed/SpawnProtocol.scala b/akka-actor-typed/src/main/scala/akka/actor/typed/SpawnProtocol.scala index a8bdaf056b..dc7bc5db6b 100644 --- a/akka-actor-typed/src/main/scala/akka/actor/typed/SpawnProtocol.scala +++ b/akka-actor-typed/src/main/scala/akka/actor/typed/SpawnProtocol.scala @@ -5,25 +5,29 @@ package akka.actor.typed import scala.annotation.tailrec - import akka.actor.typed.scaladsl.Behaviors +import akka.annotation.DoNotInherit +/** + * A message protocol for actors that support spawning a child actor when receiving a [[SpawnProtocol#Spawn]] + * message and sending back the [[ActorRef]] of the child actor. Create instances through the [[SpawnProtocol#apply]] + * or [[SpawnProtocol.create()]] factory methods. + * + * The typical usage of this is to use it as the guardian actor of the [[ActorSystem]], possibly combined with + * `Behaviors.setup` to starts some initial tasks or actors. Child actors can then be started from the outside + * by telling or asking [[SpawnProtocol#Spawn]] to the actor reference of the system. When using `ask` this is + * similar to how [[akka.actor.ActorSystem#actorOf]] can be used in untyped actors with the difference that + * a `Future` / `CompletionStage` of the `ActorRef` is returned. + * + * Stopping children is done through specific support in the protocol of the children, or stopping the entire + * spawn protocol actor. + */ object SpawnProtocol { - object Spawn { - - /** - * Special factory to make using Spawn with ask easier - */ - def apply[T](behavior: Behavior[T], name: String, props: Props): ActorRef[ActorRef[T]] => Spawn[T] = - replyTo => new Spawn(behavior, name, props, replyTo) - - /** - * Special factory to make using Spawn with ask easier. Props defaults to Props.empty - */ - def apply[T](behavior: Behavior[T], name: String): ActorRef[ActorRef[T]] => Spawn[T] = - replyTo => new Spawn(behavior, name, Props.empty, replyTo) - } + /** + * Not for user extension + */ + @DoNotInherit sealed trait Command /** * Spawn a child actor with the given `behavior` and send back the `ActorRef` of that child to the given @@ -37,12 +41,17 @@ object SpawnProtocol { * `InvalidActorNameException`, but it's better to use unique names to begin with. */ final case class Spawn[T](behavior: Behavior[T], name: String, props: Props, replyTo: ActorRef[ActorRef[T]]) - extends SpawnProtocol + extends Command /** - * Behavior implementing the [[SpawnProtocol]]. + * Java API: returns a behavior that can be commanded to spawn arbitrary children. */ - val behavior: Behavior[SpawnProtocol] = + def create(): Behavior[Command] = apply() + + /** + * Scala API: returns a behavior that can be commanded to spawn arbitrary children. + */ + def apply(): Behavior[Command] = Behaviors.receive { (ctx, msg) => msg match { case Spawn(bhvr, name, props, replyTo) => @@ -67,17 +76,3 @@ object SpawnProtocol { } } - -/** - * A message protocol for actors that support spawning a child actor when receiving a [[SpawnProtocol#Spawn]] - * message and sending back the [[ActorRef]] of the child actor. An implementation of a behavior for this - * protocol is defined in [[SpawnProtocol#behavior]]. That can be used as is or composed with other behavior - * using [[Behavior#orElse]]. - * - * The typical usage of this is to use it as the guardian actor of the [[ActorSystem]], possibly combined with - * `Behaviors.setup` to starts some initial tasks or actors. Child actors can then be started from the outside - * by telling or asking [[SpawnProtocol#Spawn]] to the actor reference of the system. When using `ask` this is - * similar to how [[akka.actor.ActorSystem#actorOf]] can be used in untyped actors with the difference that - * a `Future` / `CompletionStage` of the `ActorRef` is returned. - */ -sealed abstract class SpawnProtocol diff --git a/akka-cluster-typed/src/multi-jvm/scala/akka/cluster/typed/internal/ClusterReceptionistUnreachabilitySpec.scala b/akka-cluster-typed/src/multi-jvm/scala/akka/cluster/typed/internal/ClusterReceptionistUnreachabilitySpec.scala index 5da13395d5..5d3ddd1950 100644 --- a/akka-cluster-typed/src/multi-jvm/scala/akka/cluster/typed/internal/ClusterReceptionistUnreachabilitySpec.scala +++ b/akka-cluster-typed/src/multi-jvm/scala/akka/cluster/typed/internal/ClusterReceptionistUnreachabilitySpec.scala @@ -7,6 +7,7 @@ package akka.cluster.typed.internal import akka.actor.testkit.typed.scaladsl.TestProbe import akka.actor.typed.ActorRef import akka.actor.typed.Behavior +import akka.actor.typed.Props import akka.actor.typed.SpawnProtocol import akka.actor.typed.receptionist.Receptionist import akka.actor.typed.receptionist.ServiceKey @@ -54,11 +55,11 @@ abstract class ClusterReceptionistUnreachabilitySpec import ClusterReceptionistUnreachabilitySpec._ - val spawnActor = system.actorOf(PropsAdapter(SpawnProtocol.behavior)).toTyped[SpawnProtocol] + val spawnActor = system.actorOf(PropsAdapter(SpawnProtocol())).toTyped[SpawnProtocol.Command] def spawn[T](behavior: Behavior[T], name: String): ActorRef[T] = { implicit val timeout: Timeout = 3.seconds implicit val scheduler = typedSystem.scheduler - val f: Future[ActorRef[T]] = spawnActor.ask(ref => SpawnProtocol.Spawn(behavior, name)(ref)) + val f: Future[ActorRef[T]] = spawnActor.ask(SpawnProtocol.Spawn(behavior, name, Props.empty, _)) Await.result(f, 3.seconds) } diff --git a/akka-docs/src/main/paradox/project/migration-guide-2.5.x-2.6.x.md b/akka-docs/src/main/paradox/project/migration-guide-2.5.x-2.6.x.md index e50304ff4d..99a37d67cc 100644 --- a/akka-docs/src/main/paradox/project/migration-guide-2.5.x-2.6.x.md +++ b/akka-docs/src/main/paradox/project/migration-guide-2.5.x-2.6.x.md @@ -462,6 +462,8 @@ made before finalizing the APIs. Compared to Akka 2.5.x the source incompatible `interceptMessageType` method in `BehaviorInterceptor` is replaced with this @scala[`ClassTag`]@java[`Class`] parameter. * `Behavior.orElse` has been removed because it wasn't safe together with `narrow`. * `StashBuffer`s are now created with `Behaviors.withStash` rather than instantiating directly +* To align with the Akka Typed style guide `SpawnProtocol` is now created through @scala[`SpawnProtocol()`]@java[`SpawnProtocol.create()`], the special `Spawn` message + factories has been removed and the top level of the actor protocol is now `SpawnProtocol.Command` #### Akka Typed Stream API changes