'git mv' rst resources to md
This is mainly intended to keep the git history as neat as possible. No actual conversion yet, so now both the rst and the paradox docs are broken, which will be fixed in the next commits.
This commit is contained in:
parent
5507147073
commit
a4a0d308ad
553 changed files with 0 additions and 0 deletions
|
|
@ -1,663 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package docs.actor
|
||||
|
||||
import jdocs.actor.ImmutableMessage
|
||||
|
||||
import language.postfixOps
|
||||
|
||||
//#imports1
|
||||
import akka.actor.Actor
|
||||
import akka.actor.Props
|
||||
import akka.event.Logging
|
||||
|
||||
//#imports1
|
||||
|
||||
import scala.concurrent.Future
|
||||
import akka.actor.{ ActorRef, ActorSystem, PoisonPill, Terminated, ActorLogging }
|
||||
import org.scalatest.{ BeforeAndAfterAll, WordSpec }
|
||||
import akka.testkit._
|
||||
import akka.util._
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.Await
|
||||
import akka.Done
|
||||
import akka.actor.CoordinatedShutdown
|
||||
|
||||
//#my-actor
|
||||
class MyActor extends Actor {
|
||||
val log = Logging(context.system, this)
|
||||
|
||||
def receive = {
|
||||
case "test" => log.info("received test")
|
||||
case _ => log.info("received unknown message")
|
||||
}
|
||||
}
|
||||
//#my-actor
|
||||
|
||||
final case class DoIt(msg: ImmutableMessage)
|
||||
final case class Message(s: String)
|
||||
|
||||
//#context-actorOf
|
||||
class FirstActor extends Actor {
|
||||
val child = context.actorOf(Props[MyActor], name = "myChild")
|
||||
//#plus-some-behavior
|
||||
def receive = {
|
||||
case x => sender() ! x
|
||||
}
|
||||
//#plus-some-behavior
|
||||
}
|
||||
//#context-actorOf
|
||||
|
||||
class ActorWithArgs(arg: String) extends Actor {
|
||||
def receive = { case _ => () }
|
||||
}
|
||||
|
||||
//#actor-with-value-class-argument
|
||||
class Argument(val value: String) extends AnyVal
|
||||
class ValueClassActor(arg: Argument) extends Actor {
|
||||
def receive = { case _ => () }
|
||||
}
|
||||
|
||||
object ValueClassActor {
|
||||
def props1(arg: Argument) = Props(classOf[ValueClassActor], arg) // fails at runtime
|
||||
def props2(arg: Argument) = Props(classOf[ValueClassActor], arg.value) // ok
|
||||
def props3(arg: Argument) = Props(new ValueClassActor(arg)) // ok
|
||||
}
|
||||
//#actor-with-value-class-argument
|
||||
|
||||
class DemoActorWrapper extends Actor {
|
||||
//#props-factory
|
||||
object DemoActor {
|
||||
/**
|
||||
* Create Props for an actor of this type.
|
||||
*
|
||||
* @param magicNumber The magic number to be passed to this actor’s constructor.
|
||||
* @return a Props for creating this actor, which can then be further configured
|
||||
* (e.g. calling `.withDispatcher()` on it)
|
||||
*/
|
||||
def props(magicNumber: Int): Props = Props(new DemoActor(magicNumber))
|
||||
}
|
||||
|
||||
class DemoActor(magicNumber: Int) extends Actor {
|
||||
def receive = {
|
||||
case x: Int => sender() ! (x + magicNumber)
|
||||
}
|
||||
}
|
||||
|
||||
class SomeOtherActor extends Actor {
|
||||
// Props(new DemoActor(42)) would not be safe
|
||||
context.actorOf(DemoActor.props(42), "demo")
|
||||
// ...
|
||||
//#props-factory
|
||||
def receive = {
|
||||
case msg =>
|
||||
}
|
||||
//#props-factory
|
||||
}
|
||||
//#props-factory
|
||||
|
||||
def receive = Actor.emptyBehavior
|
||||
}
|
||||
|
||||
class ActorWithMessagesWrapper {
|
||||
//#messages-in-companion
|
||||
object MyActor {
|
||||
case class Greeting(from: String)
|
||||
case object Goodbye
|
||||
}
|
||||
class MyActor extends Actor with ActorLogging {
|
||||
import MyActor._
|
||||
def receive = {
|
||||
case Greeting(greeter) => log.info(s"I was greeted by $greeter.")
|
||||
case Goodbye => log.info("Someone said goodbye to me.")
|
||||
}
|
||||
}
|
||||
//#messages-in-companion
|
||||
|
||||
def receive = Actor.emptyBehavior
|
||||
}
|
||||
|
||||
class Hook extends Actor {
|
||||
var child: ActorRef = _
|
||||
//#preStart
|
||||
override def preStart() {
|
||||
child = context.actorOf(Props[MyActor], "child")
|
||||
}
|
||||
//#preStart
|
||||
def receive = Actor.emptyBehavior
|
||||
//#postStop
|
||||
override def postStop() {
|
||||
//#clean-up-some-resources
|
||||
()
|
||||
//#clean-up-some-resources
|
||||
}
|
||||
//#postStop
|
||||
}
|
||||
|
||||
class ReplyException extends Actor {
|
||||
def receive = {
|
||||
case _ =>
|
||||
//#reply-exception
|
||||
try {
|
||||
val result = operation()
|
||||
sender() ! result
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
sender() ! akka.actor.Status.Failure(e)
|
||||
throw e
|
||||
}
|
||||
//#reply-exception
|
||||
}
|
||||
|
||||
def operation(): String = { "Hi" }
|
||||
|
||||
}
|
||||
|
||||
class StoppingActorsWrapper {
|
||||
//#stoppingActors-actor
|
||||
class MyActor extends Actor {
|
||||
|
||||
val child: ActorRef = ???
|
||||
|
||||
def receive = {
|
||||
case "interrupt-child" =>
|
||||
context stop child
|
||||
|
||||
case "done" =>
|
||||
context stop self
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//#stoppingActors-actor
|
||||
}
|
||||
|
||||
//#gracefulStop-actor
|
||||
object Manager {
|
||||
case object Shutdown
|
||||
}
|
||||
|
||||
class Manager extends Actor {
|
||||
import Manager._
|
||||
val worker = context.watch(context.actorOf(Props[Cruncher], "worker"))
|
||||
|
||||
def receive = {
|
||||
case "job" => worker ! "crunch"
|
||||
case Shutdown =>
|
||||
worker ! PoisonPill
|
||||
context become shuttingDown
|
||||
}
|
||||
|
||||
def shuttingDown: Receive = {
|
||||
case "job" => sender() ! "service unavailable, shutting down"
|
||||
case Terminated(`worker`) =>
|
||||
context stop self
|
||||
}
|
||||
}
|
||||
//#gracefulStop-actor
|
||||
|
||||
class Cruncher extends Actor {
|
||||
def receive = {
|
||||
case "crunch" => // crunch...
|
||||
}
|
||||
}
|
||||
|
||||
//#swapper
|
||||
case object Swap
|
||||
class Swapper extends Actor {
|
||||
import context._
|
||||
val log = Logging(system, this)
|
||||
|
||||
def receive = {
|
||||
case Swap =>
|
||||
log.info("Hi")
|
||||
become({
|
||||
case Swap =>
|
||||
log.info("Ho")
|
||||
unbecome() // resets the latest 'become' (just for fun)
|
||||
}, discardOld = false) // push on top instead of replace
|
||||
}
|
||||
}
|
||||
|
||||
object SwapperApp extends App {
|
||||
val system = ActorSystem("SwapperSystem")
|
||||
val swap = system.actorOf(Props[Swapper], name = "swapper")
|
||||
swap ! Swap // logs Hi
|
||||
swap ! Swap // logs Ho
|
||||
swap ! Swap // logs Hi
|
||||
swap ! Swap // logs Ho
|
||||
swap ! Swap // logs Hi
|
||||
swap ! Swap // logs Ho
|
||||
}
|
||||
//#swapper
|
||||
|
||||
//#receive-orElse
|
||||
|
||||
trait ProducerBehavior {
|
||||
this: Actor =>
|
||||
|
||||
val producerBehavior: Receive = {
|
||||
case GiveMeThings =>
|
||||
sender() ! Give("thing")
|
||||
}
|
||||
}
|
||||
|
||||
trait ConsumerBehavior {
|
||||
this: Actor with ActorLogging =>
|
||||
|
||||
val consumerBehavior: Receive = {
|
||||
case ref: ActorRef =>
|
||||
ref ! GiveMeThings
|
||||
|
||||
case Give(thing) =>
|
||||
log.info("Got a thing! It's {}", thing)
|
||||
}
|
||||
}
|
||||
|
||||
class Producer extends Actor with ProducerBehavior {
|
||||
def receive = producerBehavior
|
||||
}
|
||||
|
||||
class Consumer extends Actor with ActorLogging with ConsumerBehavior {
|
||||
def receive = consumerBehavior
|
||||
}
|
||||
|
||||
class ProducerConsumer extends Actor with ActorLogging
|
||||
with ProducerBehavior with ConsumerBehavior {
|
||||
|
||||
def receive = producerBehavior.orElse[Any, Unit](consumerBehavior)
|
||||
}
|
||||
|
||||
// protocol
|
||||
case object GiveMeThings
|
||||
final case class Give(thing: Any)
|
||||
|
||||
//#receive-orElse
|
||||
|
||||
class ActorDocSpec extends AkkaSpec("""
|
||||
akka.loglevel = INFO
|
||||
akka.loggers = []
|
||||
""") {
|
||||
|
||||
"import context" in {
|
||||
new AnyRef {
|
||||
//#import-context
|
||||
class FirstActor extends Actor {
|
||||
import context._
|
||||
val myActor = actorOf(Props[MyActor], name = "myactor")
|
||||
def receive = {
|
||||
case x => myActor ! x
|
||||
}
|
||||
}
|
||||
//#import-context
|
||||
|
||||
val first = system.actorOf(Props(classOf[FirstActor], this), name = "first")
|
||||
system.stop(first)
|
||||
}
|
||||
}
|
||||
|
||||
"creating actor with system.actorOf" in {
|
||||
val myActor = system.actorOf(Props[MyActor])
|
||||
|
||||
// testing the actor
|
||||
|
||||
// TODO: convert docs to AkkaSpec(Map(...))
|
||||
val filter = EventFilter.custom {
|
||||
case e: Logging.Info => true
|
||||
case _ => false
|
||||
}
|
||||
system.eventStream.publish(TestEvent.Mute(filter))
|
||||
system.eventStream.subscribe(testActor, classOf[Logging.Info])
|
||||
|
||||
myActor ! "test"
|
||||
expectMsgPF(1 second) { case Logging.Info(_, _, "received test") => true }
|
||||
|
||||
myActor ! "unknown"
|
||||
expectMsgPF(1 second) { case Logging.Info(_, _, "received unknown message") => true }
|
||||
|
||||
system.eventStream.unsubscribe(testActor)
|
||||
system.eventStream.publish(TestEvent.UnMute(filter))
|
||||
|
||||
system.stop(myActor)
|
||||
}
|
||||
|
||||
"creating a Props config" in {
|
||||
//#creating-props
|
||||
import akka.actor.Props
|
||||
|
||||
val props1 = Props[MyActor]
|
||||
val props2 = Props(new ActorWithArgs("arg")) // careful, see below
|
||||
val props3 = Props(classOf[ActorWithArgs], "arg") // no support for value class arguments
|
||||
//#creating-props
|
||||
|
||||
//#creating-props-deprecated
|
||||
// NOT RECOMMENDED within another actor:
|
||||
// encourages to close over enclosing class
|
||||
val props7 = Props(new MyActor)
|
||||
//#creating-props-deprecated
|
||||
}
|
||||
|
||||
"creating actor with Props" in {
|
||||
//#system-actorOf
|
||||
import akka.actor.ActorSystem
|
||||
|
||||
// ActorSystem is a heavy object: create only one per application
|
||||
val system = ActorSystem("mySystem")
|
||||
val myActor = system.actorOf(Props[MyActor], "myactor2")
|
||||
//#system-actorOf
|
||||
shutdown(system)
|
||||
}
|
||||
|
||||
"creating actor with IndirectActorProducer" in {
|
||||
class Echo(name: String) extends Actor {
|
||||
def receive = {
|
||||
case n: Int => sender() ! name
|
||||
case message =>
|
||||
val target = testActor
|
||||
//#forward
|
||||
target forward message
|
||||
//#forward
|
||||
}
|
||||
}
|
||||
|
||||
val a: { def actorRef: ActorRef } = new AnyRef {
|
||||
val applicationContext = this
|
||||
|
||||
//#creating-indirectly
|
||||
import akka.actor.IndirectActorProducer
|
||||
|
||||
class DependencyInjector(applicationContext: AnyRef, beanName: String)
|
||||
extends IndirectActorProducer {
|
||||
|
||||
override def actorClass = classOf[Actor]
|
||||
override def produce =
|
||||
//#obtain-fresh-Actor-instance-from-DI-framework
|
||||
new Echo(beanName)
|
||||
|
||||
def this(beanName: String) = this("", beanName)
|
||||
//#obtain-fresh-Actor-instance-from-DI-framework
|
||||
}
|
||||
|
||||
val actorRef = system.actorOf(
|
||||
Props(classOf[DependencyInjector], applicationContext, "hello"),
|
||||
"helloBean")
|
||||
//#creating-indirectly
|
||||
}
|
||||
val actorRef = {
|
||||
import scala.language.reflectiveCalls
|
||||
a.actorRef
|
||||
}
|
||||
|
||||
val message = 42
|
||||
implicit val self = testActor
|
||||
//#tell
|
||||
actorRef ! message
|
||||
//#tell
|
||||
expectMsg("hello")
|
||||
actorRef ! "huhu"
|
||||
expectMsg("huhu")
|
||||
}
|
||||
|
||||
"using implicit timeout" in {
|
||||
val myActor = system.actorOf(Props[FirstActor])
|
||||
//#using-implicit-timeout
|
||||
import scala.concurrent.duration._
|
||||
import akka.util.Timeout
|
||||
import akka.pattern.ask
|
||||
implicit val timeout = Timeout(5 seconds)
|
||||
val future = myActor ? "hello"
|
||||
//#using-implicit-timeout
|
||||
Await.result(future, timeout.duration) should be("hello")
|
||||
|
||||
}
|
||||
|
||||
"using explicit timeout" in {
|
||||
val myActor = system.actorOf(Props[FirstActor])
|
||||
//#using-explicit-timeout
|
||||
import scala.concurrent.duration._
|
||||
import akka.pattern.ask
|
||||
val future = myActor.ask("hello")(5 seconds)
|
||||
//#using-explicit-timeout
|
||||
Await.result(future, 5 seconds) should be("hello")
|
||||
}
|
||||
|
||||
"using receiveTimeout" in {
|
||||
//#receive-timeout
|
||||
import akka.actor.ReceiveTimeout
|
||||
import scala.concurrent.duration._
|
||||
class MyActor extends Actor {
|
||||
// To set an initial delay
|
||||
context.setReceiveTimeout(30 milliseconds)
|
||||
def receive = {
|
||||
case "Hello" =>
|
||||
// To set in a response to a message
|
||||
context.setReceiveTimeout(100 milliseconds)
|
||||
case ReceiveTimeout =>
|
||||
// To turn it off
|
||||
context.setReceiveTimeout(Duration.Undefined)
|
||||
throw new RuntimeException("Receive timed out")
|
||||
}
|
||||
}
|
||||
//#receive-timeout
|
||||
}
|
||||
|
||||
//#hot-swap-actor
|
||||
class HotSwapActor extends Actor {
|
||||
import context._
|
||||
def angry: Receive = {
|
||||
case "foo" => sender() ! "I am already angry?"
|
||||
case "bar" => become(happy)
|
||||
}
|
||||
|
||||
def happy: Receive = {
|
||||
case "bar" => sender() ! "I am already happy :-)"
|
||||
case "foo" => become(angry)
|
||||
}
|
||||
|
||||
def receive = {
|
||||
case "foo" => become(angry)
|
||||
case "bar" => become(happy)
|
||||
}
|
||||
}
|
||||
//#hot-swap-actor
|
||||
|
||||
"using hot-swap" in {
|
||||
val actor = system.actorOf(Props(classOf[HotSwapActor], this), name = "hot")
|
||||
}
|
||||
|
||||
"using Stash" in {
|
||||
//#stash
|
||||
import akka.actor.Stash
|
||||
class ActorWithProtocol extends Actor with Stash {
|
||||
def receive = {
|
||||
case "open" =>
|
||||
unstashAll()
|
||||
context.become({
|
||||
case "write" => // do writing...
|
||||
case "close" =>
|
||||
unstashAll()
|
||||
context.unbecome()
|
||||
case msg => stash()
|
||||
}, discardOld = false) // stack on top instead of replacing
|
||||
case msg => stash()
|
||||
}
|
||||
}
|
||||
//#stash
|
||||
}
|
||||
|
||||
"using watch" in {
|
||||
new AnyRef {
|
||||
//#watch
|
||||
import akka.actor.{ Actor, Props, Terminated }
|
||||
|
||||
class WatchActor extends Actor {
|
||||
val child = context.actorOf(Props.empty, "child")
|
||||
context.watch(child) // <-- this is the only call needed for registration
|
||||
var lastSender = context.system.deadLetters
|
||||
|
||||
def receive = {
|
||||
case "kill" =>
|
||||
context.stop(child); lastSender = sender()
|
||||
case Terminated(`child`) => lastSender ! "finished"
|
||||
}
|
||||
}
|
||||
//#watch
|
||||
val a = system.actorOf(Props(classOf[WatchActor], this))
|
||||
implicit val sender = testActor
|
||||
a ! "kill"
|
||||
expectMsg("finished")
|
||||
}
|
||||
}
|
||||
|
||||
"demonstrate ActorSelection" in {
|
||||
val context = system
|
||||
//#selection-local
|
||||
// will look up this absolute path
|
||||
context.actorSelection("/user/serviceA/aggregator")
|
||||
// will look up sibling beneath same supervisor
|
||||
context.actorSelection("../joe")
|
||||
//#selection-local
|
||||
//#selection-wildcard
|
||||
// will look all children to serviceB with names starting with worker
|
||||
context.actorSelection("/user/serviceB/worker*")
|
||||
// will look up all siblings beneath same supervisor
|
||||
context.actorSelection("../*")
|
||||
//#selection-wildcard
|
||||
//#selection-remote
|
||||
context.actorSelection("akka.tcp://app@otherhost:1234/user/serviceB")
|
||||
//#selection-remote
|
||||
}
|
||||
|
||||
"using Identify" in {
|
||||
new AnyRef {
|
||||
//#identify
|
||||
import akka.actor.{ Actor, Props, Identify, ActorIdentity, Terminated }
|
||||
|
||||
class Follower extends Actor {
|
||||
val identifyId = 1
|
||||
context.actorSelection("/user/another") ! Identify(identifyId)
|
||||
|
||||
def receive = {
|
||||
case ActorIdentity(`identifyId`, Some(ref)) =>
|
||||
context.watch(ref)
|
||||
context.become(active(ref))
|
||||
case ActorIdentity(`identifyId`, None) => context.stop(self)
|
||||
|
||||
}
|
||||
|
||||
def active(another: ActorRef): Actor.Receive = {
|
||||
case Terminated(`another`) => context.stop(self)
|
||||
}
|
||||
}
|
||||
//#identify
|
||||
|
||||
val a = system.actorOf(Props.empty)
|
||||
val b = system.actorOf(Props(classOf[Follower], this))
|
||||
watch(b)
|
||||
system.stop(a)
|
||||
expectMsgType[akka.actor.Terminated].actor should be(b)
|
||||
}
|
||||
}
|
||||
|
||||
"using pattern gracefulStop" in {
|
||||
val actorRef = system.actorOf(Props[Manager])
|
||||
//#gracefulStop
|
||||
import akka.pattern.gracefulStop
|
||||
import scala.concurrent.Await
|
||||
|
||||
try {
|
||||
val stopped: Future[Boolean] = gracefulStop(actorRef, 5 seconds, Manager.Shutdown)
|
||||
Await.result(stopped, 6 seconds)
|
||||
// the actor has been stopped
|
||||
} catch {
|
||||
// the actor wasn't stopped within 5 seconds
|
||||
case e: akka.pattern.AskTimeoutException =>
|
||||
}
|
||||
//#gracefulStop
|
||||
}
|
||||
|
||||
"using pattern ask / pipeTo" in {
|
||||
val actorA, actorB, actorC, actorD = system.actorOf(Props.empty)
|
||||
//#ask-pipeTo
|
||||
import akka.pattern.{ ask, pipe }
|
||||
import system.dispatcher // The ExecutionContext that will be used
|
||||
final case class Result(x: Int, s: String, d: Double)
|
||||
case object Request
|
||||
|
||||
implicit val timeout = Timeout(5 seconds) // needed for `?` below
|
||||
|
||||
val f: Future[Result] =
|
||||
for {
|
||||
x <- ask(actorA, Request).mapTo[Int] // call pattern directly
|
||||
s <- (actorB ask Request).mapTo[String] // call by implicit conversion
|
||||
d <- (actorC ? Request).mapTo[Double] // call by symbolic name
|
||||
} yield Result(x, s, d)
|
||||
|
||||
f pipeTo actorD // .. or ..
|
||||
pipe(f) to actorD
|
||||
//#ask-pipeTo
|
||||
}
|
||||
|
||||
class Replier extends Actor {
|
||||
def receive = {
|
||||
case ref: ActorRef =>
|
||||
//#reply-with-sender
|
||||
sender().tell("reply", context.parent) // replies will go back to parent
|
||||
sender().!("reply")(context.parent) // alternative syntax (beware of the parens!)
|
||||
//#reply-with-sender
|
||||
case x =>
|
||||
//#reply-without-sender
|
||||
sender() ! x // replies will go to this actor
|
||||
//#reply-without-sender
|
||||
}
|
||||
}
|
||||
|
||||
"replying with own or other sender" in {
|
||||
val actor = system.actorOf(Props(classOf[Replier], this))
|
||||
implicit val me = testActor
|
||||
actor ! 42
|
||||
expectMsg(42)
|
||||
lastSender should be(actor)
|
||||
actor ! me
|
||||
expectMsg("reply")
|
||||
lastSender.path.toStringWithoutAddress should be("/user")
|
||||
expectMsg("reply")
|
||||
lastSender.path.toStringWithoutAddress should be("/user")
|
||||
}
|
||||
|
||||
"using ActorDSL outside of akka.actor package" in {
|
||||
import akka.actor.ActorDSL._
|
||||
actor(new Act {
|
||||
superviseWith(OneForOneStrategy() { case _ => Stop; Restart; Resume; Escalate })
|
||||
superviseWith(AllForOneStrategy() { case _ => Stop; Restart; Resume; Escalate })
|
||||
})
|
||||
}
|
||||
|
||||
"using CoordinatedShutdown" in {
|
||||
val someActor = system.actorOf(Props(classOf[Replier], this))
|
||||
//#coordinated-shutdown-addTask
|
||||
CoordinatedShutdown(system).addTask(
|
||||
CoordinatedShutdown.PhaseBeforeServiceUnbind, "someTaskName") { () =>
|
||||
import akka.pattern.ask
|
||||
import system.dispatcher
|
||||
implicit val timeout = Timeout(5.seconds)
|
||||
(someActor ? "stop").map(_ => Done)
|
||||
}
|
||||
//#coordinated-shutdown-addTask
|
||||
|
||||
//#coordinated-shutdown-jvm-hook
|
||||
CoordinatedShutdown(system).addJvmShutdownHook {
|
||||
println("custom JVM shutdown hook...")
|
||||
}
|
||||
//#coordinated-shutdown-jvm-hook
|
||||
|
||||
// don't run this
|
||||
def dummy(): Unit = {
|
||||
//#coordinated-shutdown-run
|
||||
val done: Future[Done] = CoordinatedShutdown(system).run()
|
||||
//#coordinated-shutdown-run
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2016-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package docs.actor
|
||||
|
||||
//#bytebufserializer-with-manifest
|
||||
import java.nio.ByteBuffer
|
||||
import akka.serialization.ByteBufferSerializer
|
||||
import akka.serialization.SerializerWithStringManifest
|
||||
|
||||
//#bytebufserializer-with-manifest
|
||||
|
||||
class ByteBufferSerializerDocSpec {
|
||||
|
||||
//#bytebufserializer-with-manifest
|
||||
class ExampleByteBufSerializer extends SerializerWithStringManifest with ByteBufferSerializer {
|
||||
override def identifier: Int = 1337
|
||||
override def manifest(o: AnyRef): String = "naive-toStringImpl"
|
||||
|
||||
// Implement this method for compatibility with `SerializerWithStringManifest`.
|
||||
override def toBinary(o: AnyRef): Array[Byte] = {
|
||||
// in production code, aquire this from a BufferPool
|
||||
val buf = ByteBuffer.allocate(256)
|
||||
|
||||
toBinary(o, buf)
|
||||
buf.flip()
|
||||
val bytes = new Array[Byte](buf.remaining)
|
||||
buf.get(bytes)
|
||||
bytes
|
||||
}
|
||||
|
||||
// Implement this method for compatibility with `SerializerWithStringManifest`.
|
||||
override def fromBinary(bytes: Array[Byte], manifest: String): AnyRef =
|
||||
fromBinary(ByteBuffer.wrap(bytes), manifest)
|
||||
|
||||
// Actual implementation in the ByteBuffer versions of to/fromBinary:
|
||||
override def toBinary(o: AnyRef, buf: ByteBuffer): Unit = ??? // implement actual logic here
|
||||
override def fromBinary(buf: ByteBuffer, manifest: String): AnyRef = ??? // implement actual logic here
|
||||
}
|
||||
//#bytebufserializer-with-manifest
|
||||
|
||||
}
|
||||
|
|
@ -1,231 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package docs.actor
|
||||
|
||||
import language.postfixOps
|
||||
|
||||
import akka.testkit.{ AkkaSpec => MyFavoriteTestFrameWorkPlusAkkaTestKit }
|
||||
import akka.util.ByteString
|
||||
|
||||
//#test-code
|
||||
import akka.actor.Props
|
||||
import scala.collection.immutable
|
||||
|
||||
object FSMDocSpec {
|
||||
// messages and data types
|
||||
//#test-code
|
||||
import akka.actor.ActorRef
|
||||
//#simple-events
|
||||
// received events
|
||||
final case class SetTarget(ref: ActorRef)
|
||||
final case class Queue(obj: Any)
|
||||
case object Flush
|
||||
|
||||
// sent events
|
||||
final case class Batch(obj: immutable.Seq[Any])
|
||||
//#simple-events
|
||||
//#simple-state
|
||||
// states
|
||||
sealed trait State
|
||||
case object Idle extends State
|
||||
case object Active extends State
|
||||
|
||||
sealed trait Data
|
||||
case object Uninitialized extends Data
|
||||
final case class Todo(target: ActorRef, queue: immutable.Seq[Any]) extends Data
|
||||
//#simple-state
|
||||
//#test-code
|
||||
}
|
||||
|
||||
class FSMDocSpec extends MyFavoriteTestFrameWorkPlusAkkaTestKit {
|
||||
import FSMDocSpec._
|
||||
|
||||
//#fsm-code-elided
|
||||
//#simple-imports
|
||||
import akka.actor.{ ActorRef, FSM }
|
||||
import scala.concurrent.duration._
|
||||
//#simple-imports
|
||||
//#simple-fsm
|
||||
class Buncher extends FSM[State, Data] {
|
||||
|
||||
//#fsm-body
|
||||
startWith(Idle, Uninitialized)
|
||||
|
||||
//#when-syntax
|
||||
when(Idle) {
|
||||
case Event(SetTarget(ref), Uninitialized) =>
|
||||
stay using Todo(ref, Vector.empty)
|
||||
}
|
||||
//#when-syntax
|
||||
|
||||
//#transition-elided
|
||||
onTransition {
|
||||
case Active -> Idle =>
|
||||
stateData match {
|
||||
case Todo(ref, queue) => ref ! Batch(queue)
|
||||
case _ => // nothing to do
|
||||
}
|
||||
}
|
||||
//#transition-elided
|
||||
//#when-syntax
|
||||
|
||||
when(Active, stateTimeout = 1 second) {
|
||||
case Event(Flush | StateTimeout, t: Todo) =>
|
||||
goto(Idle) using t.copy(queue = Vector.empty)
|
||||
}
|
||||
//#when-syntax
|
||||
|
||||
//#unhandled-elided
|
||||
whenUnhandled {
|
||||
// common code for both states
|
||||
case Event(Queue(obj), t @ Todo(_, v)) =>
|
||||
goto(Active) using t.copy(queue = v :+ obj)
|
||||
|
||||
case Event(e, s) =>
|
||||
log.warning("received unhandled request {} in state {}/{}", e, stateName, s)
|
||||
stay
|
||||
}
|
||||
//#unhandled-elided
|
||||
//#fsm-body
|
||||
|
||||
initialize()
|
||||
}
|
||||
//#simple-fsm
|
||||
object DemoCode {
|
||||
trait StateType
|
||||
case object SomeState extends StateType
|
||||
case object Processing extends StateType
|
||||
case object Error extends StateType
|
||||
case object Idle extends StateType
|
||||
case object Active extends StateType
|
||||
|
||||
class Dummy extends FSM[StateType, Int] {
|
||||
class X
|
||||
val newData = 42
|
||||
object WillDo
|
||||
object Tick
|
||||
|
||||
//#modifier-syntax
|
||||
when(SomeState) {
|
||||
case Event(msg, _) =>
|
||||
goto(Processing) using (newData) forMax (5 seconds) replying (WillDo)
|
||||
}
|
||||
//#modifier-syntax
|
||||
|
||||
//#transition-syntax
|
||||
onTransition {
|
||||
case Idle -> Active => setTimer("timeout", Tick, 1 second, repeat = true)
|
||||
case Active -> _ => cancelTimer("timeout")
|
||||
case x -> Idle => log.info("entering Idle from " + x)
|
||||
}
|
||||
//#transition-syntax
|
||||
|
||||
//#alt-transition-syntax
|
||||
onTransition(handler _)
|
||||
|
||||
def handler(from: StateType, to: StateType) {
|
||||
// handle it here ...
|
||||
}
|
||||
//#alt-transition-syntax
|
||||
|
||||
//#stop-syntax
|
||||
when(Error) {
|
||||
case Event("stop", _) =>
|
||||
// do cleanup ...
|
||||
stop()
|
||||
}
|
||||
//#stop-syntax
|
||||
|
||||
//#transform-syntax
|
||||
when(SomeState)(transform {
|
||||
case Event(bytes: ByteString, read) => stay using (read + bytes.length)
|
||||
} using {
|
||||
case s @ FSM.State(state, read, timeout, stopReason, replies) if read > 1000 =>
|
||||
goto(Processing)
|
||||
})
|
||||
//#transform-syntax
|
||||
|
||||
//#alt-transform-syntax
|
||||
val processingTrigger: PartialFunction[State, State] = {
|
||||
case s @ FSM.State(state, read, timeout, stopReason, replies) if read > 1000 =>
|
||||
goto(Processing)
|
||||
}
|
||||
|
||||
when(SomeState)(transform {
|
||||
case Event(bytes: ByteString, read) => stay using (read + bytes.length)
|
||||
} using processingTrigger)
|
||||
//#alt-transform-syntax
|
||||
|
||||
//#termination-syntax
|
||||
onTermination {
|
||||
case StopEvent(FSM.Normal, state, data) => // ...
|
||||
case StopEvent(FSM.Shutdown, state, data) => // ...
|
||||
case StopEvent(FSM.Failure(cause), state, data) => // ...
|
||||
}
|
||||
//#termination-syntax
|
||||
|
||||
//#unhandled-syntax
|
||||
whenUnhandled {
|
||||
case Event(x: X, data) =>
|
||||
log.info("Received unhandled event: " + x)
|
||||
stay
|
||||
case Event(msg, _) =>
|
||||
log.warning("Received unknown event: " + msg)
|
||||
goto(Error)
|
||||
}
|
||||
//#unhandled-syntax
|
||||
|
||||
}
|
||||
|
||||
//#logging-fsm
|
||||
import akka.actor.LoggingFSM
|
||||
class MyFSM extends LoggingFSM[StateType, Data] {
|
||||
//#body-elided
|
||||
override def logDepth = 12
|
||||
onTermination {
|
||||
case StopEvent(FSM.Failure(_), state, data) =>
|
||||
val lastEvents = getLog.mkString("\n\t")
|
||||
log.warning("Failure in state " + state + " with data " + data + "\n" +
|
||||
"Events leading up to this point:\n\t" + lastEvents)
|
||||
}
|
||||
// ...
|
||||
//#body-elided
|
||||
}
|
||||
//#logging-fsm
|
||||
|
||||
}
|
||||
//#fsm-code-elided
|
||||
|
||||
"simple finite state machine" must {
|
||||
|
||||
"demonstrate NullFunction" in {
|
||||
class A extends FSM[Int, Null] {
|
||||
val SomeState = 0
|
||||
//#NullFunction
|
||||
when(SomeState)(FSM.NullFunction)
|
||||
//#NullFunction
|
||||
}
|
||||
}
|
||||
|
||||
"batch correctly" in {
|
||||
val buncher = system.actorOf(Props(classOf[Buncher], this))
|
||||
buncher ! SetTarget(testActor)
|
||||
buncher ! Queue(42)
|
||||
buncher ! Queue(43)
|
||||
expectMsg(Batch(immutable.Seq(42, 43)))
|
||||
buncher ! Queue(44)
|
||||
buncher ! Flush
|
||||
buncher ! Queue(45)
|
||||
expectMsg(Batch(immutable.Seq(44)))
|
||||
expectMsg(Batch(immutable.Seq(45)))
|
||||
}
|
||||
|
||||
"not batch if uninitialized" in {
|
||||
val buncher = system.actorOf(Props(classOf[Buncher], this))
|
||||
buncher ! Queue(42)
|
||||
expectNoMsg
|
||||
}
|
||||
}
|
||||
}
|
||||
//#test-code
|
||||
|
|
@ -1,299 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package docs.actor
|
||||
|
||||
import language.postfixOps
|
||||
|
||||
//#all
|
||||
//#imports
|
||||
import akka.actor._
|
||||
import akka.actor.SupervisorStrategy._
|
||||
import scala.concurrent.duration._
|
||||
import akka.util.Timeout
|
||||
import akka.event.LoggingReceive
|
||||
import akka.pattern.{ ask, pipe }
|
||||
import com.typesafe.config.ConfigFactory
|
||||
//#imports
|
||||
|
||||
/**
|
||||
* Runs the sample
|
||||
*/
|
||||
object FaultHandlingDocSample extends App {
|
||||
import Worker._
|
||||
|
||||
val config = ConfigFactory.parseString("""
|
||||
akka.loglevel = "DEBUG"
|
||||
akka.actor.debug {
|
||||
receive = on
|
||||
lifecycle = on
|
||||
}
|
||||
""")
|
||||
|
||||
val system = ActorSystem("FaultToleranceSample", config)
|
||||
val worker = system.actorOf(Props[Worker], name = "worker")
|
||||
val listener = system.actorOf(Props[Listener], name = "listener")
|
||||
// start the work and listen on progress
|
||||
// note that the listener is used as sender of the tell,
|
||||
// i.e. it will receive replies from the worker
|
||||
worker.tell(Start, sender = listener)
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens on progress from the worker and shuts down the system when enough
|
||||
* work has been done.
|
||||
*/
|
||||
class Listener extends Actor with ActorLogging {
|
||||
import Worker._
|
||||
// If we don't get any progress within 15 seconds then the service is unavailable
|
||||
context.setReceiveTimeout(15 seconds)
|
||||
|
||||
def receive = {
|
||||
case Progress(percent) =>
|
||||
log.info("Current progress: {} %", percent)
|
||||
if (percent >= 100.0) {
|
||||
log.info("That's all, shutting down")
|
||||
context.system.terminate()
|
||||
}
|
||||
|
||||
case ReceiveTimeout =>
|
||||
// No progress within 15 seconds, ServiceUnavailable
|
||||
log.error("Shutting down due to unavailable service")
|
||||
context.system.terminate()
|
||||
}
|
||||
}
|
||||
|
||||
//#messages
|
||||
object Worker {
|
||||
case object Start
|
||||
case object Do
|
||||
final case class Progress(percent: Double)
|
||||
}
|
||||
//#messages
|
||||
|
||||
/**
|
||||
* Worker performs some work when it receives the `Start` message.
|
||||
* It will continuously notify the sender of the `Start` message
|
||||
* of current ``Progress``. The `Worker` supervise the `CounterService`.
|
||||
*/
|
||||
class Worker extends Actor with ActorLogging {
|
||||
import Worker._
|
||||
import CounterService._
|
||||
implicit val askTimeout = Timeout(5 seconds)
|
||||
|
||||
// Stop the CounterService child if it throws ServiceUnavailable
|
||||
override val supervisorStrategy = OneForOneStrategy() {
|
||||
case _: CounterService.ServiceUnavailable => Stop
|
||||
}
|
||||
|
||||
// The sender of the initial Start message will continuously be notified
|
||||
// about progress
|
||||
var progressListener: Option[ActorRef] = None
|
||||
val counterService = context.actorOf(Props[CounterService], name = "counter")
|
||||
val totalCount = 51
|
||||
import context.dispatcher // Use this Actors' Dispatcher as ExecutionContext
|
||||
|
||||
def receive = LoggingReceive {
|
||||
case Start if progressListener.isEmpty =>
|
||||
progressListener = Some(sender())
|
||||
context.system.scheduler.schedule(Duration.Zero, 1 second, self, Do)
|
||||
|
||||
case Do =>
|
||||
counterService ! Increment(1)
|
||||
counterService ! Increment(1)
|
||||
counterService ! Increment(1)
|
||||
|
||||
// Send current progress to the initial sender
|
||||
counterService ? GetCurrentCount map {
|
||||
case CurrentCount(_, count) => Progress(100.0 * count / totalCount)
|
||||
} pipeTo progressListener.get
|
||||
}
|
||||
}
|
||||
|
||||
//#messages
|
||||
object CounterService {
|
||||
final case class Increment(n: Int)
|
||||
sealed abstract class GetCurrentCount
|
||||
case object GetCurrentCount extends GetCurrentCount
|
||||
final case class CurrentCount(key: String, count: Long)
|
||||
class ServiceUnavailable(msg: String) extends RuntimeException(msg)
|
||||
|
||||
private case object Reconnect
|
||||
}
|
||||
//#messages
|
||||
|
||||
/**
|
||||
* Adds the value received in `Increment` message to a persistent
|
||||
* counter. Replies with `CurrentCount` when it is asked for `CurrentCount`.
|
||||
* `CounterService` supervise `Storage` and `Counter`.
|
||||
*/
|
||||
class CounterService extends Actor {
|
||||
import CounterService._
|
||||
import Counter._
|
||||
import Storage._
|
||||
|
||||
// Restart the storage child when StorageException is thrown.
|
||||
// After 3 restarts within 5 seconds it will be stopped.
|
||||
override val supervisorStrategy = OneForOneStrategy(
|
||||
maxNrOfRetries = 3,
|
||||
withinTimeRange = 5 seconds) {
|
||||
case _: Storage.StorageException => Restart
|
||||
}
|
||||
|
||||
val key = self.path.name
|
||||
var storage: Option[ActorRef] = None
|
||||
var counter: Option[ActorRef] = None
|
||||
var backlog = IndexedSeq.empty[(ActorRef, Any)]
|
||||
val MaxBacklog = 10000
|
||||
|
||||
import context.dispatcher // Use this Actors' Dispatcher as ExecutionContext
|
||||
|
||||
override def preStart() {
|
||||
initStorage()
|
||||
}
|
||||
|
||||
/**
|
||||
* The child storage is restarted in case of failure, but after 3 restarts,
|
||||
* and still failing it will be stopped. Better to back-off than continuously
|
||||
* failing. When it has been stopped we will schedule a Reconnect after a delay.
|
||||
* Watch the child so we receive Terminated message when it has been terminated.
|
||||
*/
|
||||
def initStorage() {
|
||||
storage = Some(context.watch(context.actorOf(Props[Storage], name = "storage")))
|
||||
// Tell the counter, if any, to use the new storage
|
||||
counter foreach { _ ! UseStorage(storage) }
|
||||
// We need the initial value to be able to operate
|
||||
storage.get ! Get(key)
|
||||
}
|
||||
|
||||
def receive = LoggingReceive {
|
||||
|
||||
case Entry(k, v) if k == key && counter == None =>
|
||||
// Reply from Storage of the initial value, now we can create the Counter
|
||||
val c = context.actorOf(Props(classOf[Counter], key, v))
|
||||
counter = Some(c)
|
||||
// Tell the counter to use current storage
|
||||
c ! UseStorage(storage)
|
||||
// and send the buffered backlog to the counter
|
||||
for ((replyTo, msg) <- backlog) c.tell(msg, sender = replyTo)
|
||||
backlog = IndexedSeq.empty
|
||||
|
||||
case msg: Increment => forwardOrPlaceInBacklog(msg)
|
||||
|
||||
case msg: GetCurrentCount => forwardOrPlaceInBacklog(msg)
|
||||
|
||||
case Terminated(actorRef) if Some(actorRef) == storage =>
|
||||
// After 3 restarts the storage child is stopped.
|
||||
// We receive Terminated because we watch the child, see initStorage.
|
||||
storage = None
|
||||
// Tell the counter that there is no storage for the moment
|
||||
counter foreach { _ ! UseStorage(None) }
|
||||
// Try to re-establish storage after while
|
||||
context.system.scheduler.scheduleOnce(10 seconds, self, Reconnect)
|
||||
|
||||
case Reconnect =>
|
||||
// Re-establish storage after the scheduled delay
|
||||
initStorage()
|
||||
}
|
||||
|
||||
def forwardOrPlaceInBacklog(msg: Any) {
|
||||
// We need the initial value from storage before we can start delegate to
|
||||
// the counter. Before that we place the messages in a backlog, to be sent
|
||||
// to the counter when it is initialized.
|
||||
counter match {
|
||||
case Some(c) => c forward msg
|
||||
case None =>
|
||||
if (backlog.size >= MaxBacklog)
|
||||
throw new ServiceUnavailable(
|
||||
"CounterService not available, lack of initial value")
|
||||
backlog :+= (sender() -> msg)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//#messages
|
||||
object Counter {
|
||||
final case class UseStorage(storage: Option[ActorRef])
|
||||
}
|
||||
//#messages
|
||||
|
||||
/**
|
||||
* The in memory count variable that will send current
|
||||
* value to the `Storage`, if there is any storage
|
||||
* available at the moment.
|
||||
*/
|
||||
class Counter(key: String, initialValue: Long) extends Actor {
|
||||
import Counter._
|
||||
import CounterService._
|
||||
import Storage._
|
||||
|
||||
var count = initialValue
|
||||
var storage: Option[ActorRef] = None
|
||||
|
||||
def receive = LoggingReceive {
|
||||
case UseStorage(s) =>
|
||||
storage = s
|
||||
storeCount()
|
||||
|
||||
case Increment(n) =>
|
||||
count += n
|
||||
storeCount()
|
||||
|
||||
case GetCurrentCount =>
|
||||
sender() ! CurrentCount(key, count)
|
||||
|
||||
}
|
||||
|
||||
def storeCount() {
|
||||
// Delegate dangerous work, to protect our valuable state.
|
||||
// We can continue without storage.
|
||||
storage foreach { _ ! Store(Entry(key, count)) }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//#messages
|
||||
object Storage {
|
||||
final case class Store(entry: Entry)
|
||||
final case class Get(key: String)
|
||||
final case class Entry(key: String, value: Long)
|
||||
class StorageException(msg: String) extends RuntimeException(msg)
|
||||
}
|
||||
//#messages
|
||||
|
||||
/**
|
||||
* Saves key/value pairs to persistent storage when receiving `Store` message.
|
||||
* Replies with current value when receiving `Get` message.
|
||||
* Will throw StorageException if the underlying data store is out of order.
|
||||
*/
|
||||
class Storage extends Actor {
|
||||
import Storage._
|
||||
|
||||
val db = DummyDB
|
||||
|
||||
def receive = LoggingReceive {
|
||||
case Store(Entry(key, count)) => db.save(key, count)
|
||||
case Get(key) => sender() ! Entry(key, db.load(key).getOrElse(0L))
|
||||
}
|
||||
}
|
||||
|
||||
//#dummydb
|
||||
object DummyDB {
|
||||
import Storage.StorageException
|
||||
private var db = Map[String, Long]()
|
||||
|
||||
@throws(classOf[StorageException])
|
||||
def save(key: String, value: Long): Unit = synchronized {
|
||||
if (11 <= value && value <= 14)
|
||||
throw new StorageException("Simulated store failure " + value)
|
||||
db += (key -> value)
|
||||
}
|
||||
|
||||
@throws(classOf[StorageException])
|
||||
def load(key: String): Option[Long] = synchronized {
|
||||
db.get(key)
|
||||
}
|
||||
}
|
||||
//#dummydb
|
||||
//#all
|
||||
|
|
@ -1,184 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package docs.actor
|
||||
|
||||
import language.postfixOps
|
||||
import akka.actor.{ ActorSystem, ActorRef, Props, Terminated }
|
||||
import FaultHandlingDocSpec._
|
||||
|
||||
//#testkit
|
||||
import com.typesafe.config.{ Config, ConfigFactory }
|
||||
import org.scalatest.{ FlatSpecLike, Matchers, BeforeAndAfterAll }
|
||||
import akka.testkit.{ TestActors, TestKit, ImplicitSender, EventFilter }
|
||||
|
||||
//#testkit
|
||||
object FaultHandlingDocSpec {
|
||||
//#supervisor
|
||||
//#child
|
||||
import akka.actor.Actor
|
||||
|
||||
//#child
|
||||
class Supervisor extends Actor {
|
||||
//#strategy
|
||||
import akka.actor.OneForOneStrategy
|
||||
import akka.actor.SupervisorStrategy._
|
||||
import scala.concurrent.duration._
|
||||
|
||||
override val supervisorStrategy =
|
||||
OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 minute) {
|
||||
case _: ArithmeticException => Resume
|
||||
case _: NullPointerException => Restart
|
||||
case _: IllegalArgumentException => Stop
|
||||
case _: Exception => Escalate
|
||||
}
|
||||
//#strategy
|
||||
|
||||
def receive = {
|
||||
case p: Props => sender() ! context.actorOf(p)
|
||||
}
|
||||
}
|
||||
//#supervisor
|
||||
|
||||
//#supervisor2
|
||||
class Supervisor2 extends Actor {
|
||||
//#strategy2
|
||||
import akka.actor.OneForOneStrategy
|
||||
import akka.actor.SupervisorStrategy._
|
||||
import scala.concurrent.duration._
|
||||
|
||||
override val supervisorStrategy =
|
||||
OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 minute) {
|
||||
case _: ArithmeticException => Resume
|
||||
case _: NullPointerException => Restart
|
||||
case _: IllegalArgumentException => Stop
|
||||
case _: Exception => Escalate
|
||||
}
|
||||
//#strategy2
|
||||
|
||||
def receive = {
|
||||
case p: Props => sender() ! context.actorOf(p)
|
||||
}
|
||||
// override default to kill all children during restart
|
||||
override def preRestart(cause: Throwable, msg: Option[Any]) {}
|
||||
}
|
||||
//#supervisor2
|
||||
|
||||
class Supervisor3 extends Actor {
|
||||
//#default-strategy-fallback
|
||||
import akka.actor.OneForOneStrategy
|
||||
import akka.actor.SupervisorStrategy._
|
||||
import scala.concurrent.duration._
|
||||
|
||||
override val supervisorStrategy =
|
||||
OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 minute) {
|
||||
case _: ArithmeticException => Resume
|
||||
case t =>
|
||||
super.supervisorStrategy.decider.applyOrElse(t, (_: Any) => Escalate)
|
||||
}
|
||||
//#default-strategy-fallback
|
||||
|
||||
def receive = Actor.emptyBehavior
|
||||
}
|
||||
|
||||
//#child
|
||||
class Child extends Actor {
|
||||
var state = 0
|
||||
def receive = {
|
||||
case ex: Exception => throw ex
|
||||
case x: Int => state = x
|
||||
case "get" => sender() ! state
|
||||
}
|
||||
}
|
||||
//#child
|
||||
|
||||
val testConf: Config = ConfigFactory.parseString("""
|
||||
akka {
|
||||
loggers = ["akka.testkit.TestEventListener"]
|
||||
}
|
||||
""")
|
||||
}
|
||||
//#testkit
|
||||
class FaultHandlingDocSpec(_system: ActorSystem) extends TestKit(_system)
|
||||
with ImplicitSender with FlatSpecLike with Matchers with BeforeAndAfterAll {
|
||||
|
||||
def this() = this(ActorSystem(
|
||||
"FaultHandlingDocSpec",
|
||||
ConfigFactory.parseString("""
|
||||
akka {
|
||||
loggers = ["akka.testkit.TestEventListener"]
|
||||
loglevel = "WARNING"
|
||||
}
|
||||
""")))
|
||||
|
||||
override def afterAll {
|
||||
TestKit.shutdownActorSystem(system)
|
||||
}
|
||||
|
||||
"A supervisor" must "apply the chosen strategy for its child" in {
|
||||
//#testkit
|
||||
|
||||
//#create
|
||||
val supervisor = system.actorOf(Props[Supervisor], "supervisor")
|
||||
|
||||
supervisor ! Props[Child]
|
||||
val child = expectMsgType[ActorRef] // retrieve answer from TestKit’s testActor
|
||||
//#create
|
||||
EventFilter.warning(occurrences = 1) intercept {
|
||||
//#resume
|
||||
child ! 42 // set state to 42
|
||||
child ! "get"
|
||||
expectMsg(42)
|
||||
|
||||
child ! new ArithmeticException // crash it
|
||||
child ! "get"
|
||||
expectMsg(42)
|
||||
//#resume
|
||||
}
|
||||
EventFilter[NullPointerException](occurrences = 1) intercept {
|
||||
//#restart
|
||||
child ! new NullPointerException // crash it harder
|
||||
child ! "get"
|
||||
expectMsg(0)
|
||||
//#restart
|
||||
}
|
||||
EventFilter[IllegalArgumentException](occurrences = 1) intercept {
|
||||
//#stop
|
||||
watch(child) // have testActor watch “child”
|
||||
child ! new IllegalArgumentException // break it
|
||||
expectMsgPF() { case Terminated(`child`) => () }
|
||||
//#stop
|
||||
}
|
||||
EventFilter[Exception]("CRASH", occurrences = 2) intercept {
|
||||
//#escalate-kill
|
||||
supervisor ! Props[Child] // create new child
|
||||
val child2 = expectMsgType[ActorRef]
|
||||
watch(child2)
|
||||
child2 ! "get" // verify it is alive
|
||||
expectMsg(0)
|
||||
|
||||
child2 ! new Exception("CRASH") // escalate failure
|
||||
expectMsgPF() {
|
||||
case t @ Terminated(`child2`) if t.existenceConfirmed => ()
|
||||
}
|
||||
//#escalate-kill
|
||||
//#escalate-restart
|
||||
val supervisor2 = system.actorOf(Props[Supervisor2], "supervisor2")
|
||||
|
||||
supervisor2 ! Props[Child]
|
||||
val child3 = expectMsgType[ActorRef]
|
||||
|
||||
child3 ! 23
|
||||
child3 ! "get"
|
||||
expectMsg(23)
|
||||
|
||||
child3 ! new Exception("CRASH")
|
||||
child3 ! "get"
|
||||
expectMsg(0)
|
||||
//#escalate-restart
|
||||
}
|
||||
//#testkit
|
||||
// code here
|
||||
}
|
||||
}
|
||||
//#testkit
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package docs.actor
|
||||
|
||||
import akka.actor.{ Props, Actor }
|
||||
import akka.testkit.{ ImplicitSender, AkkaSpec }
|
||||
|
||||
object InitializationDocSpec {
|
||||
|
||||
class PreStartInitExample extends Actor {
|
||||
override def receive = {
|
||||
case _ => // Ignore
|
||||
}
|
||||
|
||||
//#preStartInit
|
||||
override def preStart(): Unit = {
|
||||
// Initialize children here
|
||||
}
|
||||
|
||||
// Overriding postRestart to disable the call to preStart()
|
||||
// after restarts
|
||||
override def postRestart(reason: Throwable): Unit = ()
|
||||
|
||||
// The default implementation of preRestart() stops all the children
|
||||
// of the actor. To opt-out from stopping the children, we
|
||||
// have to override preRestart()
|
||||
override def preRestart(reason: Throwable, message: Option[Any]): Unit = {
|
||||
// Keep the call to postStop(), but no stopping of children
|
||||
postStop()
|
||||
}
|
||||
//#preStartInit
|
||||
}
|
||||
|
||||
class MessageInitExample extends Actor {
|
||||
//#messageInit
|
||||
var initializeMe: Option[String] = None
|
||||
|
||||
override def receive = {
|
||||
case "init" =>
|
||||
initializeMe = Some("Up and running")
|
||||
context.become(initialized, discardOld = true)
|
||||
|
||||
}
|
||||
|
||||
def initialized: Receive = {
|
||||
case "U OK?" => initializeMe foreach { sender() ! _ }
|
||||
}
|
||||
//#messageInit
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class InitializationDocSpec extends AkkaSpec with ImplicitSender {
|
||||
import InitializationDocSpec._
|
||||
|
||||
"Message based initialization example" must {
|
||||
|
||||
"work correctly" in {
|
||||
val example = system.actorOf(Props[MessageInitExample], "messageInitExample")
|
||||
val probe = "U OK?"
|
||||
|
||||
example ! probe
|
||||
expectNoMsg()
|
||||
|
||||
example ! "init"
|
||||
example ! probe
|
||||
expectMsg("Up and running")
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package docs.actor
|
||||
|
||||
import akka.actor.{ Actor, Props }
|
||||
import docs.CompileOnlySpec
|
||||
import org.scalatest.WordSpec
|
||||
|
||||
//#props-edge-cases-value-class
|
||||
case class MyValueClass(v: Int) extends AnyVal
|
||||
|
||||
//#props-edge-cases-value-class
|
||||
|
||||
class PropsEdgeCaseSpec extends WordSpec with CompileOnlySpec {
|
||||
"value-class-edge-case-example" in compileOnlySpec {
|
||||
//#props-edge-cases-value-class-example
|
||||
class ValueActor(value: MyValueClass) extends Actor {
|
||||
def receive = {
|
||||
case multiplier: Long => sender() ! (value.v * multiplier)
|
||||
}
|
||||
}
|
||||
val valueClassProp = Props(classOf[ValueActor], MyValueClass(5)) // Unsupported
|
||||
//#props-edge-cases-value-class-example
|
||||
|
||||
//#props-edge-cases-default-values
|
||||
class DefaultValueActor(a: Int, b: Int = 5) extends Actor {
|
||||
def receive = {
|
||||
case x: Int => sender() ! ((a + x) * b)
|
||||
}
|
||||
}
|
||||
|
||||
val defaultValueProp1 = Props(classOf[DefaultValueActor], 2.0) // Unsupported
|
||||
|
||||
class DefaultValueActor2(b: Int = 5) extends Actor {
|
||||
def receive = {
|
||||
case x: Int => sender() ! (x * b)
|
||||
}
|
||||
}
|
||||
val defaultValueProp2 = Props[DefaultValueActor2] // Unsupported
|
||||
val defaultValueProp3 = Props(classOf[DefaultValueActor2]) // Unsupported
|
||||
//#props-edge-cases-default-values
|
||||
}
|
||||
}
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package docs.actor
|
||||
|
||||
import language.postfixOps
|
||||
|
||||
//#imports1
|
||||
import akka.actor.Actor
|
||||
import akka.actor.Props
|
||||
import scala.concurrent.duration._
|
||||
|
||||
//#imports1
|
||||
|
||||
import org.scalatest.{ BeforeAndAfterAll, WordSpec }
|
||||
import org.scalatest.Matchers
|
||||
import akka.testkit._
|
||||
|
||||
class SchedulerDocSpec extends AkkaSpec(Map("akka.loglevel" -> "INFO")) {
|
||||
"schedule a one-off task" in {
|
||||
//#schedule-one-off-message
|
||||
//Use the system's dispatcher as ExecutionContext
|
||||
import system.dispatcher
|
||||
|
||||
//Schedules to send the "foo"-message to the testActor after 50ms
|
||||
system.scheduler.scheduleOnce(50 milliseconds, testActor, "foo")
|
||||
//#schedule-one-off-message
|
||||
|
||||
expectMsg(1 second, "foo")
|
||||
|
||||
//#schedule-one-off-thunk
|
||||
//Schedules a function to be executed (send a message to the testActor) after 50ms
|
||||
system.scheduler.scheduleOnce(50 milliseconds) {
|
||||
testActor ! System.currentTimeMillis
|
||||
}
|
||||
//#schedule-one-off-thunk
|
||||
|
||||
}
|
||||
|
||||
"schedule a recurring task" in {
|
||||
new AnyRef {
|
||||
//#schedule-recurring
|
||||
val Tick = "tick"
|
||||
class TickActor extends Actor {
|
||||
def receive = {
|
||||
case Tick => //Do something
|
||||
}
|
||||
}
|
||||
val tickActor = system.actorOf(Props(classOf[TickActor], this))
|
||||
//Use system's dispatcher as ExecutionContext
|
||||
import system.dispatcher
|
||||
|
||||
//This will schedule to send the Tick-message
|
||||
//to the tickActor after 0ms repeating every 50ms
|
||||
val cancellable =
|
||||
system.scheduler.schedule(
|
||||
0 milliseconds,
|
||||
50 milliseconds,
|
||||
tickActor,
|
||||
Tick)
|
||||
|
||||
//This cancels further Ticks to be sent
|
||||
cancellable.cancel()
|
||||
//#schedule-recurring
|
||||
system.stop(tickActor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package docs.actor
|
||||
|
||||
class SharedMutableStateDocSpec {
|
||||
|
||||
//#mutable-state
|
||||
import akka.actor.{ Actor, ActorRef }
|
||||
import akka.pattern.ask
|
||||
import akka.util.Timeout
|
||||
import scala.concurrent.Future
|
||||
import scala.concurrent.duration._
|
||||
import scala.language.postfixOps
|
||||
import scala.collection.mutable
|
||||
|
||||
case class Message(msg: String)
|
||||
|
||||
class EchoActor extends Actor {
|
||||
def receive = {
|
||||
case msg => sender() ! msg
|
||||
}
|
||||
}
|
||||
|
||||
class CleanUpActor extends Actor {
|
||||
def receive = {
|
||||
case set: mutable.Set[_] => set.clear()
|
||||
}
|
||||
}
|
||||
|
||||
class MyActor(echoActor: ActorRef, cleanUpActor: ActorRef) extends Actor {
|
||||
var state = ""
|
||||
val mySet = mutable.Set[String]()
|
||||
|
||||
def expensiveCalculation(actorRef: ActorRef): String = {
|
||||
// this is a very costly operation
|
||||
"Meaning of live is 42"
|
||||
}
|
||||
|
||||
def expensiveCalculation(): String = {
|
||||
// this is a very costly operation
|
||||
"Meaning of live is 42"
|
||||
}
|
||||
|
||||
def receive = {
|
||||
case _ =>
|
||||
|
||||
//Wrong ways
|
||||
implicit val ec = context.dispatcher
|
||||
implicit val timeout = Timeout(5 seconds) // needed for `?` below
|
||||
|
||||
// Very bad, shared mutable state,
|
||||
// will break your application in weird ways
|
||||
Future { state = "This will race" }
|
||||
((echoActor ? Message("With this other one")).mapTo[Message])
|
||||
.foreach { received => state = received.msg }
|
||||
|
||||
// Very bad, shared mutable object,
|
||||
// the other actor cand mutate your own state,
|
||||
// or worse, you might get weird race conditions
|
||||
cleanUpActor ! mySet
|
||||
|
||||
// Very bad, "sender" changes for every message,
|
||||
// shared mutable state bug
|
||||
Future { expensiveCalculation(sender()) }
|
||||
|
||||
//Right ways
|
||||
|
||||
// Completely safe, "self" is OK to close over
|
||||
// and it's an ActorRef, which is thread-safe
|
||||
Future { expensiveCalculation() } foreach { self ! _ }
|
||||
|
||||
// Completely safe, we close over a fixed value
|
||||
// and it's an ActorRef, which is thread-safe
|
||||
val currentSender = sender()
|
||||
Future { expensiveCalculation(currentSender) }
|
||||
}
|
||||
}
|
||||
//#mutable-state
|
||||
}
|
||||
|
|
@ -1,227 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package docs.actor
|
||||
|
||||
//#imports
|
||||
import java.lang.String.{ valueOf => println }
|
||||
|
||||
import akka.actor.{ ActorContext, ActorRef, TypedActor, TypedProps }
|
||||
import akka.routing.RoundRobinGroup
|
||||
import akka.testkit._
|
||||
|
||||
import scala.concurrent.{ Future, Await }
|
||||
import scala.concurrent.duration._
|
||||
//#imports
|
||||
|
||||
//Mr funny man avoids printing to stdout AND keeping docs alright
|
||||
import java.lang.String.{ valueOf => println }
|
||||
|
||||
//#typed-actor-iface
|
||||
trait Squarer {
|
||||
//#typed-actor-iface-methods
|
||||
def squareDontCare(i: Int): Unit //fire-forget
|
||||
|
||||
def square(i: Int): Future[Int] //non-blocking send-request-reply
|
||||
|
||||
def squareNowPlease(i: Int): Option[Int] //blocking send-request-reply
|
||||
|
||||
def squareNow(i: Int): Int //blocking send-request-reply
|
||||
|
||||
@throws(classOf[Exception]) //declare it or you will get an UndeclaredThrowableException
|
||||
def squareTry(i: Int): Int //blocking send-request-reply with possible exception
|
||||
//#typed-actor-iface-methods
|
||||
}
|
||||
//#typed-actor-iface
|
||||
|
||||
//#typed-actor-impl
|
||||
class SquarerImpl(val name: String) extends Squarer {
|
||||
|
||||
def this() = this("default")
|
||||
//#typed-actor-impl-methods
|
||||
def squareDontCare(i: Int): Unit = i * i //Nobody cares :(
|
||||
|
||||
def square(i: Int): Future[Int] = Future.successful(i * i)
|
||||
|
||||
def squareNowPlease(i: Int): Option[Int] = Some(i * i)
|
||||
|
||||
def squareNow(i: Int): Int = i * i
|
||||
|
||||
def squareTry(i: Int): Int = throw new Exception("Catch me!")
|
||||
//#typed-actor-impl-methods
|
||||
}
|
||||
//#typed-actor-impl
|
||||
//#typed-actor-supercharge
|
||||
trait Foo {
|
||||
def doFoo(times: Int): Unit = println("doFoo(" + times + ")")
|
||||
}
|
||||
|
||||
trait Bar {
|
||||
def doBar(str: String): Future[String] =
|
||||
Future.successful(str.toUpperCase)
|
||||
}
|
||||
|
||||
class FooBar extends Foo with Bar
|
||||
//#typed-actor-supercharge
|
||||
|
||||
//#typed-router-types
|
||||
trait HasName {
|
||||
def name(): String
|
||||
}
|
||||
|
||||
class Named extends HasName {
|
||||
import scala.util.Random
|
||||
private val id = Random.nextInt(1024)
|
||||
|
||||
def name(): String = "name-" + id
|
||||
}
|
||||
//#typed-router-types
|
||||
|
||||
class TypedActorDocSpec extends AkkaSpec(Map("akka.loglevel" -> "INFO")) {
|
||||
|
||||
"get the TypedActor extension" in {
|
||||
val someReference: AnyRef = null
|
||||
|
||||
try {
|
||||
//#typed-actor-extension-tools
|
||||
|
||||
import akka.actor.TypedActor
|
||||
|
||||
//Returns the Typed Actor Extension
|
||||
val extension = TypedActor(system) //system is an instance of ActorSystem
|
||||
|
||||
//Returns whether the reference is a Typed Actor Proxy or not
|
||||
TypedActor(system).isTypedActor(someReference)
|
||||
|
||||
//Returns the backing Akka Actor behind an external Typed Actor Proxy
|
||||
TypedActor(system).getActorRefFor(someReference)
|
||||
|
||||
//Returns the current ActorContext,
|
||||
// method only valid within methods of a TypedActor implementation
|
||||
val c: ActorContext = TypedActor.context
|
||||
|
||||
//Returns the external proxy of the current Typed Actor,
|
||||
// method only valid within methods of a TypedActor implementation
|
||||
val s: Squarer = TypedActor.self[Squarer]
|
||||
|
||||
//Returns a contextual instance of the Typed Actor Extension
|
||||
//this means that if you create other Typed Actors with this,
|
||||
//they will become children to the current Typed Actor.
|
||||
TypedActor(TypedActor.context)
|
||||
|
||||
//#typed-actor-extension-tools
|
||||
} catch {
|
||||
case e: Exception => //dun care
|
||||
}
|
||||
}
|
||||
|
||||
"create a typed actor" in {
|
||||
//#typed-actor-create1
|
||||
val mySquarer: Squarer =
|
||||
TypedActor(system).typedActorOf(TypedProps[SquarerImpl]())
|
||||
//#typed-actor-create1
|
||||
//#typed-actor-create2
|
||||
val otherSquarer: Squarer =
|
||||
TypedActor(system).typedActorOf(TypedProps(
|
||||
classOf[Squarer],
|
||||
new SquarerImpl("foo")), "name")
|
||||
//#typed-actor-create2
|
||||
|
||||
//#typed-actor-calls
|
||||
//#typed-actor-call-oneway
|
||||
mySquarer.squareDontCare(10)
|
||||
//#typed-actor-call-oneway
|
||||
|
||||
//#typed-actor-call-future
|
||||
val fSquare = mySquarer.square(10) //A Future[Int]
|
||||
//#typed-actor-call-future
|
||||
|
||||
//#typed-actor-call-option
|
||||
val oSquare = mySquarer.squareNowPlease(10) //Option[Int]
|
||||
//#typed-actor-call-option
|
||||
|
||||
//#typed-actor-call-strict
|
||||
val iSquare = mySquarer.squareNow(10) //Int
|
||||
//#typed-actor-call-strict
|
||||
//#typed-actor-calls
|
||||
|
||||
Await.result(fSquare, 3.seconds) should be(100)
|
||||
|
||||
oSquare should be(Some(100))
|
||||
|
||||
iSquare should be(100)
|
||||
|
||||
//#typed-actor-stop
|
||||
TypedActor(system).stop(mySquarer)
|
||||
//#typed-actor-stop
|
||||
|
||||
//#typed-actor-poisonpill
|
||||
TypedActor(system).poisonPill(otherSquarer)
|
||||
//#typed-actor-poisonpill
|
||||
}
|
||||
|
||||
"proxy any ActorRef" in {
|
||||
val actorRefToRemoteActor: ActorRef = system.deadLetters
|
||||
//#typed-actor-remote
|
||||
val typedActor: Foo with Bar =
|
||||
TypedActor(system).
|
||||
typedActorOf(
|
||||
TypedProps[FooBar],
|
||||
actorRefToRemoteActor)
|
||||
//Use "typedActor" as a FooBar
|
||||
//#typed-actor-remote
|
||||
}
|
||||
|
||||
"create hierarchies" in {
|
||||
try {
|
||||
//#typed-actor-hierarchy
|
||||
//Inside your Typed Actor
|
||||
val childSquarer: Squarer =
|
||||
TypedActor(TypedActor.context).typedActorOf(TypedProps[SquarerImpl]())
|
||||
//Use "childSquarer" as a Squarer
|
||||
//#typed-actor-hierarchy
|
||||
} catch {
|
||||
case e: Exception => //ignore
|
||||
}
|
||||
}
|
||||
|
||||
"supercharge" in {
|
||||
//#typed-actor-supercharge-usage
|
||||
val awesomeFooBar: Foo with Bar =
|
||||
TypedActor(system).typedActorOf(TypedProps[FooBar]())
|
||||
|
||||
awesomeFooBar.doFoo(10)
|
||||
val f = awesomeFooBar.doBar("yes")
|
||||
|
||||
TypedActor(system).poisonPill(awesomeFooBar)
|
||||
//#typed-actor-supercharge-usage
|
||||
Await.result(f, 3.seconds) should be("YES")
|
||||
}
|
||||
|
||||
"typed router pattern" in {
|
||||
//#typed-router
|
||||
def namedActor(): HasName = TypedActor(system).typedActorOf(TypedProps[Named]())
|
||||
|
||||
// prepare routees
|
||||
val routees: List[HasName] = List.fill(5) { namedActor() }
|
||||
val routeePaths = routees map { r =>
|
||||
TypedActor(system).getActorRefFor(r).path.toStringWithoutAddress
|
||||
}
|
||||
|
||||
// prepare untyped router
|
||||
val router: ActorRef = system.actorOf(RoundRobinGroup(routeePaths).props())
|
||||
|
||||
// prepare typed proxy, forwarding MethodCall messages to `router`
|
||||
val typedRouter: HasName =
|
||||
TypedActor(system).typedActorOf(TypedProps[Named](), actorRef = router)
|
||||
|
||||
println("actor was: " + typedRouter.name()) // name-184
|
||||
println("actor was: " + typedRouter.name()) // name-753
|
||||
println("actor was: " + typedRouter.name()) // name-320
|
||||
println("actor was: " + typedRouter.name()) // name-164
|
||||
//#typed-router
|
||||
|
||||
routees foreach { TypedActor(system).poisonPill(_) }
|
||||
TypedActor(system).poisonPill(router)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package docs.actor
|
||||
|
||||
import akka.actor._
|
||||
import scala.collection.mutable.ListBuffer
|
||||
|
||||
/**
|
||||
* Requirements are as follows:
|
||||
* The first thing the actor needs to do, is to subscribe to a channel of events,
|
||||
* Then it must replay (process) all "old" events
|
||||
* Then it has to wait for a GoAhead signal to begin processing the new events
|
||||
* It mustn't "miss" events that happen between catching up with the old events and getting the GoAhead signal
|
||||
*/
|
||||
class UnnestedReceives extends Actor {
|
||||
import context.become
|
||||
//If you need to store sender/senderFuture you can change it to ListBuffer[(Any, Channel)]
|
||||
val queue = new ListBuffer[Any]()
|
||||
|
||||
//This message processes a message/event
|
||||
def process(msg: Any): Unit = println("processing: " + msg)
|
||||
//This method subscribes the actor to the event bus
|
||||
def subscribe() {} //Your external stuff
|
||||
//This method retrieves all prior messages/events
|
||||
def allOldMessages() = List()
|
||||
|
||||
override def preStart {
|
||||
//We override preStart to be sure that the first message the actor gets is
|
||||
//'Replay, that message will start to be processed _after_ the actor is started
|
||||
self ! 'Replay
|
||||
//Then we subscribe to the stream of messages/events
|
||||
subscribe()
|
||||
}
|
||||
|
||||
def receive = {
|
||||
case 'Replay => //Our first message should be a 'Replay message, all others are invalid
|
||||
allOldMessages() foreach process //Process all old messages/events
|
||||
become { //Switch behavior to look for the GoAhead signal
|
||||
case 'GoAhead => //When we get the GoAhead signal we process all our buffered messages/events
|
||||
queue foreach process
|
||||
queue.clear
|
||||
become { //Then we change behaviour to process incoming messages/events as they arrive
|
||||
case msg => process(msg)
|
||||
}
|
||||
case msg => //While we haven't gotten the GoAhead signal, buffer all incoming messages
|
||||
queue += msg //Here you have full control, you can handle overflow etc
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue