From b2b4f64d977fa443d2c92f7c5f1c8db1b5ec668a Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Thu, 16 Mar 2017 13:47:05 +0100 Subject: [PATCH] convert typed IntroSpec to new API, #22293 --- .../src/main/scala/akka/actor/Scheduler.scala | 24 +++--- .../code/docs/akka/typed/IntroSpec.scala | 78 ++++++++++--------- akka-docs/rst/scala/typed.rst | 37 +++------ .../scala/akka/typed/internal/ActorCell.scala | 1 + 4 files changed, 67 insertions(+), 73 deletions(-) diff --git a/akka-actor/src/main/scala/akka/actor/Scheduler.scala b/akka-actor/src/main/scala/akka/actor/Scheduler.scala index 749b0559db..3a70f9fbbc 100644 --- a/akka-actor/src/main/scala/akka/actor/Scheduler.scala +++ b/akka-actor/src/main/scala/akka/actor/Scheduler.scala @@ -30,10 +30,10 @@ private final case class SchedulerException(msg: String) extends akka.AkkaExcept * 1) the system’s com.typesafe.config.Config (from system.settings.config) * 2) a akka.event.LoggingAdapter * 3) a java.util.concurrent.ThreadFactory - * - * Please note that this scheduler implementation is higly optimised for high-throughput - * and high-frequency events. It is not to be confused with long-term schedulers such as - * Quartz. The scheduler will throw an exception if attempts are made to schedule too far + * + * Please note that this scheduler implementation is higly optimised for high-throughput + * and high-frequency events. It is not to be confused with long-term schedulers such as + * Quartz. The scheduler will throw an exception if attempts are made to schedule too far * into the future (which by default is around 8 months (`Int.MaxValue` seconds). */ trait Scheduler { @@ -96,9 +96,9 @@ trait Scheduler { * If the `Runnable` throws an exception the repeated scheduling is aborted, * i.e. the function will not be invoked any more. * - * @throws IllegalArgumentException if the given delays exceed the maximum + * @throws IllegalArgumentException if the given delays exceed the maximum * reach (calculated as: `delay / tickNanos > Int.MaxValue`). - * + * * Java API */ def schedule( @@ -110,9 +110,9 @@ trait Scheduler { * Schedules a message to be sent once with a delay, i.e. a time period that has * to pass before the message is sent. * - * @throws IllegalArgumentException if the given delays exceed the maximum + * @throws IllegalArgumentException if the given delays exceed the maximum * reach (calculated as: `delay / tickNanos > Int.MaxValue`). - * + * * Java & Scala API */ final def scheduleOnce( @@ -129,9 +129,9 @@ trait Scheduler { * Schedules a function to be run once with a delay, i.e. a time period that has * to pass before the function is run. * - * @throws IllegalArgumentException if the given delays exceed the maximum + * @throws IllegalArgumentException if the given delays exceed the maximum * reach (calculated as: `delay / tickNanos > Int.MaxValue`). - * + * * Scala API */ final def scheduleOnce(delay: FiniteDuration)(f: ⇒ Unit)( @@ -143,9 +143,9 @@ trait Scheduler { * Schedules a Runnable to be run once with a delay, i.e. a time period that * has to pass before the runnable is executed. * - * @throws IllegalArgumentException if the given delays exceed the maximum + * @throws IllegalArgumentException if the given delays exceed the maximum * reach (calculated as: `delay / tickNanos > Int.MaxValue`). - * + * * Java & Scala API */ def scheduleOnce( diff --git a/akka-docs/rst/scala/code/docs/akka/typed/IntroSpec.scala b/akka-docs/rst/scala/code/docs/akka/typed/IntroSpec.scala index ace52e1b18..ef6e70be5c 100644 --- a/akka-docs/rst/scala/code/docs/akka/typed/IntroSpec.scala +++ b/akka-docs/rst/scala/code/docs/akka/typed/IntroSpec.scala @@ -5,7 +5,7 @@ package docs.akka.typed //#imports import akka.typed._ -import akka.typed.ScalaDSL._ +import akka.typed.scaladsl.Actor._ import akka.typed.scaladsl.AskPattern._ import scala.concurrent.Future import scala.concurrent.duration._ @@ -21,7 +21,7 @@ object IntroSpec { final case class Greet(whom: String, replyTo: ActorRef[Greeted]) final case class Greeted(whom: String) - val greeter = Static[Greet] { msg => + val greeter = Stateless[Greet] { (_, msg) ⇒ println(s"Hello ${msg.whom}!") msg.replyTo ! Greeted(msg.whom) } @@ -50,22 +50,21 @@ object IntroSpec { //#chatroom-protocol //#chatroom-behavior - val behavior: Behavior[GetSession] = - ContextAware[Command] { ctx => - var sessions = List.empty[ActorRef[SessionEvent]] - - Static { - case GetSession(screenName, client) => - sessions ::= client + def chatRoom(sessions: List[ActorRef[SessionEvent]] = List.empty): Behavior[Command] = + Stateful[Command] { (ctx, msg) ⇒ + msg match { + case GetSession(screenName, client) ⇒ val wrapper = ctx.spawnAdapter { - p: PostMessage => PostSessionMessage(screenName, p.message) + p: PostMessage ⇒ PostSessionMessage(screenName, p.message) } client ! SessionGranted(wrapper) - case PostSessionMessage(screenName, message) => + chatRoom(client :: sessions) + case PostSessionMessage(screenName, message) ⇒ val mp = MessagePosted(screenName, message) sessions foreach (_ ! mp) + Same } - }.narrow // only expose GetSession to the outside + } //#chatroom-behavior } //#chatroom-actor @@ -76,6 +75,7 @@ class IntroSpec extends TypedSpec { import IntroSpec._ def `must say hello`(): Unit = { + // TODO Implicits.global is not something we would like to encourage in docs //#hello-world import HelloWorld._ // using global pool since we want to run tasks after system.terminate @@ -86,8 +86,8 @@ class IntroSpec extends TypedSpec { val future: Future[Greeted] = system ? (Greet("world", _)) for { - greeting <- future.recover { case ex => ex.getMessage } - done <- { println(s"result: $greeting"); system.terminate() } + greeting ← future.recover { case ex ⇒ ex.getMessage } + done ← { println(s"result: $greeting"); system.terminate() } } println("system terminated") //#hello-world } @@ -96,32 +96,40 @@ class IntroSpec extends TypedSpec { //#chatroom-gabbler import ChatRoom._ - val gabbler: Behavior[SessionEvent] = - Total { - case SessionDenied(reason) => - println(s"cannot start chat room session: $reason") - Stopped - case SessionGranted(handle) => - handle ! PostMessage("Hello World!") - Same - case MessagePosted(screenName, message) => - println(s"message has been posted by '$screenName': $message") - Stopped + val gabbler = + Stateful[SessionEvent] { (_, msg) ⇒ + msg match { + case SessionDenied(reason) ⇒ + println(s"cannot start chat room session: $reason") + Stopped + case SessionGranted(handle) ⇒ + handle ! PostMessage("Hello World!") + Same + case MessagePosted(screenName, message) ⇒ + println(s"message has been posted by '$screenName': $message") + Stopped + } } //#chatroom-gabbler //#chatroom-main val main: Behavior[akka.NotUsed] = - Full { - case Sig(ctx, PreStart) => - val chatRoom = ctx.spawn(ChatRoom.behavior, "chatroom") - val gabblerRef = ctx.spawn(gabbler, "gabbler") - ctx.watch(gabblerRef) - chatRoom ! GetSession("ol’ Gabbler", gabblerRef) - Same - case Sig(_, Terminated(ref)) => - Stopped - } + SignalOrMessage( + signal = { (ctx, sig) ⇒ + sig match { + case PreStart ⇒ + val chatRoom = ctx.spawn(ChatRoom.chatRoom(), "chatroom") + val gabblerRef = ctx.spawn(gabbler, "gabbler") + ctx.watch(gabblerRef) + chatRoom ! GetSession("ol’ Gabbler", gabblerRef) + Same + case Terminated(ref) ⇒ + Stopped + case _ ⇒ + Unhandled + } + }, + mesg = (_, _) ⇒ Unhandled) val system = ActorSystem("ChatRoomDemo", main) Await.result(system.whenTerminated, 1.second) diff --git a/akka-docs/rst/scala/typed.rst b/akka-docs/rst/scala/typed.rst index 22d5a87c66..6bf2f526ca 100644 --- a/akka-docs/rst/scala/typed.rst +++ b/akka-docs/rst/scala/typed.rst @@ -30,8 +30,8 @@ supplies so that the :class:`HelloWorld` Actor can send back the confirmation message. The behavior of the Actor is defined as the :meth:`greeter` value with the help -of the :class:`Static` behavior constructor—there are many different ways of -formulating behaviors as we shall see in the following. The “static” behavior +of the :class:`Stateless` behavior constructor—there are many different ways of +formulating behaviors as we shall see in the following. The “stateless” behavior is not capable of changing in response to a message, it will stay the same until the Actor is stopped by its parent. @@ -175,10 +175,11 @@ as the following: .. includecode:: code/docs/akka/typed/IntroSpec.scala#chatroom-behavior -The core of this behavior is again static, the chat room itself does not change +The core of this behavior is stateful, the chat room itself does not change into something else when sessions are established, but we introduce a variable -that tracks the opened sessions. When a new :class:`GetSession` command comes -in we add that client to the list and then we need to create the session’s +that tracks the opened sessions. Note that by using a method parameter a ``var`` +is not needed. When a new :class:`GetSession` command comes in we add that client to the +list that is in the returned behavior. Then we also need to create the session’s :class:`ActorRef` that will be used to post messages. In this case we want to create a very simple Actor that just repackages the :class:`PostMessage` command into a :class:`PostSessionMessage` command which also includes the @@ -194,15 +195,8 @@ clients. But we do not want to give the ability to send :class:`PostSessionMessage` commands to arbitrary clients, we reserve that right to the wrappers we create—otherwise clients could pose as completely different screen names (imagine the :class:`GetSession` protocol to include -authentication information to further secure this). Therefore we narrow the -behavior down to only accepting :class:`GetSession` commands before exposing it -to the world, hence the type of the ``behavior`` value is -:class:`Behavior[GetSession]` instead of :class:`Behavior[Command]`. - -Narrowing the type of a behavior is always a safe operation since it only -restricts what clients can do. If we were to widen the type then clients could -send other messages that were not foreseen while writing the source code for -the behavior. +authentication information to further secure this). Therefore :class:`PostSessionMessage` +has ``private`` visibility and can't be created outside the actor. If we did not care about securing the correspondence between a session and a screen name then we could change the protocol such that :class:`PostMessage` is @@ -216,13 +210,6 @@ former simply speaks more languages than the latter. The opposite would be problematic, so passing an :class:`ActorRef[PostSessionMessage]` where :class:`ActorRef[Command]` is required will lead to a type error. -The final piece of this behavior definition is the :class:`ContextAware` -decorator that we use in order to obtain access to the :class:`ActorContext` -within the :class:`Static` behavior definition. This decorator invokes the -provided function when the first message is received and thereby creates the -real behavior that will be used going forward—the decorator is discarded after -it has done its job. - Trying it out ------------- @@ -261,11 +248,9 @@ Actor will perform its job on its own accord, we do not need to send messages from the outside, so we declare it to be of type ``NotUsed``. Actors receive not only external messages, they also are notified of certain system events, so-called Signals. In order to get access to those we choose to implement this -particular one using the :class:`Full` behavior decorator. The name stems from -the fact that within this we have full access to all aspects of the Actor. The -provided function will be invoked for signals (wrapped in :class:`Sig`) or user -messages (wrapped in :class:`Msg`) and the wrapper also contains a reference to -the :class:`ActorContext`. +particular one using the :class:`SignalOrMessage` behavior decorator. The +provided ``signal`` function will be invoked for signals (subclasses of :class:`Signal`) +or the ``mesg`` function for user messages. This particular main Actor reacts to two signals: when it is started it will first receive the :class:`PreStart` signal, upon which the chat room and the diff --git a/akka-typed/src/main/scala/akka/typed/internal/ActorCell.scala b/akka-typed/src/main/scala/akka/typed/internal/ActorCell.scala index 0bed92475e..67cb6ab60a 100644 --- a/akka-typed/src/main/scala/akka/typed/internal/ActorCell.scala +++ b/akka-typed/src/main/scala/akka/typed/internal/ActorCell.scala @@ -106,6 +106,7 @@ private[typed] class ActorCell[T]( val dispatcher = deployment.firstOrElse[DispatcherSelector](DispatcherFromExecutionContext(executionContext)) val capacity = deployment.firstOrElse(MailboxCapacity(system.settings.DefaultMailboxCapacity)) val cell = new ActorCell[U](system, Behavior.validateAsInitial(behavior), system.dispatchers.lookup(dispatcher), capacity.capacity, self) + // TODO uid is still needed val ref = new LocalActorRef[U](self.path / name, cell) cell.setSelf(ref) childrenMap = childrenMap.updated(name, ref)