Skip to content

Commit

Permalink
convert typed IntroSpec to new API, #22293
Browse files Browse the repository at this point in the history
  • Loading branch information
patriknw committed Mar 16, 2017
1 parent 4368bed commit d9c07be
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 71 deletions.
24 changes: 12 additions & 12 deletions akka-actor/src/main/scala/akka/actor/Scheduler.scala
Expand Up @@ -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 {
Expand Down Expand Up @@ -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(
Expand All @@ -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(
Expand All @@ -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)(
Expand All @@ -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(
Expand Down
78 changes: 43 additions & 35 deletions akka-docs/rst/scala/code/docs/akka/typed/IntroSpec.scala
Expand Up @@ -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._
Expand All @@ -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)
}
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
}
Expand All @@ -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)
Expand Down
32 changes: 8 additions & 24 deletions akka-docs/rst/scala/typed.rst
Expand Up @@ -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.

Expand Down Expand Up @@ -175,7 +175,7 @@ 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
Expand All @@ -194,15 +194,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
Expand All @@ -216,13 +209,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
-------------

Expand Down Expand Up @@ -261,11 +247,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
Expand Down
Expand Up @@ -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)
Expand Down

0 comments on commit d9c07be

Please sign in to comment.