Move all doc examples out of the akka-package to avoid use of private APIs. See #2092
This commit is contained in:
parent
09469b73e1
commit
0eae9d8d22
85 changed files with 105 additions and 105 deletions
387
akka-docs/scala/code/docs/actor/ActorDocSpec.scala
Normal file
387
akka-docs/scala/code/docs/actor/ActorDocSpec.scala
Normal file
|
|
@ -0,0 +1,387 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package docs.actor
|
||||
|
||||
//#imports1
|
||||
import akka.actor.Actor
|
||||
import akka.actor.Props
|
||||
import akka.event.Logging
|
||||
|
||||
//#imports1
|
||||
|
||||
import akka.dispatch.Future
|
||||
import akka.actor.{ ActorRef, ActorSystem }
|
||||
import org.scalatest.{ BeforeAndAfterAll, WordSpec }
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import akka.testkit._
|
||||
import akka.util._
|
||||
import akka.util.duration._
|
||||
import akka.actor.Actor.Receive
|
||||
import akka.dispatch.Await
|
||||
|
||||
//#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
|
||||
|
||||
case class DoIt(msg: ImmutableMessage)
|
||||
case class Message(s: String)
|
||||
|
||||
//#context-actorOf
|
||||
class FirstActor extends Actor {
|
||||
val myActor = context.actorOf(Props[MyActor], name = "myactor")
|
||||
//#context-actorOf
|
||||
def receive = {
|
||||
case x ⇒ sender ! x
|
||||
}
|
||||
}
|
||||
|
||||
class AnonymousActor extends Actor {
|
||||
//#anonymous-actor
|
||||
def receive = {
|
||||
case m: DoIt ⇒
|
||||
context.actorOf(Props(new Actor {
|
||||
def receive = {
|
||||
case DoIt(msg) ⇒
|
||||
val replyMsg = doSomeDangerousWork(msg)
|
||||
sender ! replyMsg
|
||||
context.stop(self)
|
||||
}
|
||||
def doSomeDangerousWork(msg: ImmutableMessage): String = { "done" }
|
||||
})) forward m
|
||||
}
|
||||
//#anonymous-actor
|
||||
}
|
||||
|
||||
//#system-actorOf
|
||||
object Main extends App {
|
||||
val system = ActorSystem("MySystem")
|
||||
val myActor = system.actorOf(Props[MyActor], name = "myactor")
|
||||
//#system-actorOf
|
||||
}
|
||||
|
||||
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" }
|
||||
|
||||
}
|
||||
|
||||
//#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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
abstract class GenericActor extends Actor {
|
||||
// to be defined in subclassing actor
|
||||
def specificMessageHandler: Receive
|
||||
|
||||
// generic message handler
|
||||
def genericMessageHandler: Receive = {
|
||||
case event ⇒ printf("generic: %s\n", event)
|
||||
}
|
||||
|
||||
def receive = specificMessageHandler orElse genericMessageHandler
|
||||
}
|
||||
|
||||
class SpecificActor extends GenericActor {
|
||||
def specificMessageHandler = {
|
||||
case event: MyMsg ⇒ printf("specific: %s\n", event.subject)
|
||||
}
|
||||
}
|
||||
|
||||
case class MyMsg(subject: String)
|
||||
//#receive-orElse
|
||||
|
||||
//#receive-orElse2
|
||||
trait ComposableActor extends Actor {
|
||||
private var receives: List[Receive] = List()
|
||||
protected def registerReceive(receive: Receive) {
|
||||
receives = receive :: receives
|
||||
}
|
||||
|
||||
def receive = receives reduce { _ orElse _ }
|
||||
}
|
||||
|
||||
class MyComposableActor extends ComposableActor {
|
||||
override def preStart() {
|
||||
registerReceive({
|
||||
case "foo" ⇒ /* Do something */
|
||||
})
|
||||
|
||||
registerReceive({
|
||||
case "bar" ⇒ /* Do something */
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//#receive-orElse2
|
||||
class ActorDocSpec extends AkkaSpec(Map("akka.loglevel" -> "INFO")) {
|
||||
|
||||
"import context" in {
|
||||
//#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(new FirstActor), name = "first")
|
||||
system.stop(first)
|
||||
|
||||
}
|
||||
|
||||
"creating actor with AkkaSpec.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 actor with constructor" in {
|
||||
class MyActor(arg: String) extends Actor {
|
||||
def receive = { case _ ⇒ () }
|
||||
}
|
||||
|
||||
//#creating-constructor
|
||||
// allows passing in arguments to the MyActor constructor
|
||||
val myActor = system.actorOf(Props(new MyActor("...")), name = "myactor")
|
||||
//#creating-constructor
|
||||
|
||||
system.stop(myActor)
|
||||
}
|
||||
|
||||
"creating a Props config" in {
|
||||
//#creating-props-config
|
||||
import akka.actor.Props
|
||||
val props1 = Props()
|
||||
val props2 = Props[MyActor]
|
||||
val props3 = Props(new MyActor)
|
||||
val props4 = Props(
|
||||
creator = { () ⇒ new MyActor },
|
||||
dispatcher = "my-dispatcher")
|
||||
val props5 = props1.withCreator(new MyActor)
|
||||
val props6 = props5.withDispatcher("my-dispatcher")
|
||||
//#creating-props-config
|
||||
}
|
||||
|
||||
"creating actor with Props" in {
|
||||
//#creating-props
|
||||
import akka.actor.Props
|
||||
val myActor = system.actorOf(Props[MyActor].withDispatcher("my-dispatcher"), name = "myactor2")
|
||||
//#creating-props
|
||||
|
||||
system.stop(myActor)
|
||||
}
|
||||
|
||||
"using implicit timeout" in {
|
||||
val myActor = system.actorOf(Props(new FirstActor))
|
||||
//#using-implicit-timeout
|
||||
import akka.util.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) must be("hello")
|
||||
|
||||
}
|
||||
|
||||
"using explicit timeout" in {
|
||||
val myActor = system.actorOf(Props(new FirstActor))
|
||||
//#using-explicit-timeout
|
||||
import akka.util.duration._
|
||||
import akka.pattern.ask
|
||||
val future = myActor.ask("hello")(5 seconds)
|
||||
//#using-explicit-timeout
|
||||
Await.result(future, 5 seconds) must be("hello")
|
||||
}
|
||||
|
||||
"using receiveTimeout" in {
|
||||
//#receive-timeout
|
||||
import akka.actor.ReceiveTimeout
|
||||
import akka.util.duration._
|
||||
class MyActor extends Actor {
|
||||
context.setReceiveTimeout(30 milliseconds)
|
||||
def receive = {
|
||||
case "Hello" ⇒ //...
|
||||
case ReceiveTimeout ⇒ throw new RuntimeException("received timeout")
|
||||
}
|
||||
}
|
||||
//#receive-timeout
|
||||
}
|
||||
|
||||
"using hot-swap" in {
|
||||
//#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
|
||||
|
||||
val actor = system.actorOf(Props(new HotSwapActor), name = "hot")
|
||||
}
|
||||
|
||||
"using watch" in {
|
||||
//#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 = system.deadLetters
|
||||
|
||||
def receive = {
|
||||
case "kill" ⇒ context.stop(child); lastSender = sender
|
||||
case Terminated(`child`) ⇒ lastSender ! "finished"
|
||||
}
|
||||
}
|
||||
//#watch
|
||||
val a = system.actorOf(Props(new WatchActor))
|
||||
implicit val sender = testActor
|
||||
a ! "kill"
|
||||
expectMsg("finished")
|
||||
}
|
||||
|
||||
"using pattern gracefulStop" in {
|
||||
val actorRef = system.actorOf(Props[MyActor])
|
||||
//#gracefulStop
|
||||
import akka.pattern.gracefulStop
|
||||
import akka.dispatch.Await
|
||||
|
||||
try {
|
||||
val stopped: Future[Boolean] = gracefulStop(actorRef, 5 seconds)(system)
|
||||
Await.result(stopped, 6 seconds)
|
||||
// the actor has been stopped
|
||||
} catch {
|
||||
case e: akka.pattern.AskTimeoutException ⇒ // the actor wasn't stopped within 5 seconds
|
||||
}
|
||||
//#gracefulStop
|
||||
}
|
||||
|
||||
"using pattern ask / pipeTo" in {
|
||||
val actorA, actorB, actorC, actorD = system.actorOf(Props.empty)
|
||||
//#ask-pipeTo
|
||||
import akka.pattern.{ ask, pipe }
|
||||
|
||||
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 manifest[String] // call by implicit conversion
|
||||
d ← actorC ? Request mapTo manifest[Double] // call by symbolic name
|
||||
} yield Result(x, s, d)
|
||||
|
||||
f pipeTo actorD // .. or ..
|
||||
pipe(f) to actorD
|
||||
//#ask-pipeTo
|
||||
}
|
||||
|
||||
"replying with own or other sender" in {
|
||||
val actor = system.actorOf(Props(new 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
|
||||
}
|
||||
}))
|
||||
implicit val me = testActor
|
||||
actor ! 42
|
||||
expectMsg(42)
|
||||
lastSender must be === actor
|
||||
actor ! me
|
||||
expectMsg("reply")
|
||||
lastSender must be === system.actorFor("/user")
|
||||
expectMsg("reply")
|
||||
lastSender must be === system.actorFor("/user")
|
||||
}
|
||||
|
||||
}
|
||||
209
akka-docs/scala/code/docs/actor/FSMDocSpec.scala
Normal file
209
akka-docs/scala/code/docs/actor/FSMDocSpec.scala
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package docs.actor
|
||||
|
||||
//#test-code
|
||||
import akka.testkit.AkkaSpec
|
||||
import akka.actor.Props
|
||||
|
||||
class FSMDocSpec extends AkkaSpec {
|
||||
|
||||
"simple finite state machine" must {
|
||||
//#fsm-code-elided
|
||||
//#simple-imports
|
||||
import akka.actor.{ Actor, ActorRef, FSM }
|
||||
import akka.util.duration._
|
||||
//#simple-imports
|
||||
//#simple-events
|
||||
// received events
|
||||
case class SetTarget(ref: ActorRef)
|
||||
case class Queue(obj: Any)
|
||||
case object Flush
|
||||
|
||||
// sent events
|
||||
case class Batch(obj: 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
|
||||
case class Todo(target: ActorRef, queue: Seq[Any]) extends Data
|
||||
//#simple-state
|
||||
//#simple-fsm
|
||||
class Buncher extends Actor with 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)
|
||||
}
|
||||
}
|
||||
//#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 Actor with 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, 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: Array[Byte], read) ⇒ stay using (read + bytes.length)
|
||||
case Event(bytes: List[Byte], read) ⇒ stay using (read + bytes.size)
|
||||
} 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: Array[Byte], read) ⇒ stay using (read + bytes.length)
|
||||
case Event(bytes: List[Byte], read) ⇒ stay using (read + bytes.size)
|
||||
} 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 Actor with 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
|
||||
|
||||
"batch correctly" in {
|
||||
val buncher = system.actorOf(Props(new Buncher))
|
||||
buncher ! SetTarget(testActor)
|
||||
buncher ! Queue(42)
|
||||
buncher ! Queue(43)
|
||||
expectMsg(Batch(Seq(42, 43)))
|
||||
buncher ! Queue(44)
|
||||
buncher ! Flush
|
||||
buncher ! Queue(45)
|
||||
expectMsg(Batch(Seq(44)))
|
||||
expectMsg(Batch(Seq(45)))
|
||||
}
|
||||
|
||||
"batch not if uninitialized" in {
|
||||
val buncher = system.actorOf(Props(new Buncher))
|
||||
buncher ! Queue(42)
|
||||
expectNoMsg
|
||||
}
|
||||
}
|
||||
}
|
||||
//#test-code
|
||||
289
akka-docs/scala/code/docs/actor/FaultHandlingDocSample.scala
Normal file
289
akka-docs/scala/code/docs/actor/FaultHandlingDocSample.scala
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package docs.actor
|
||||
|
||||
//#all
|
||||
//#imports
|
||||
import akka.actor._
|
||||
import akka.actor.SupervisorStrategy._
|
||||
import akka.util.duration._
|
||||
import akka.util.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.shutdown()
|
||||
}
|
||||
|
||||
case ReceiveTimeout ⇒
|
||||
// No progress within 15 seconds, ServiceUnavailable
|
||||
log.error("Shutting down due to unavailable service")
|
||||
context.system.shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
//#messages
|
||||
object Worker {
|
||||
case object Start
|
||||
case object Do
|
||||
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
|
||||
|
||||
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 {
|
||||
case class Increment(n: Int)
|
||||
case object GetCurrentCount
|
||||
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
|
||||
|
||||
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(new 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(n) ⇒ 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 = backlog :+ (sender, msg)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//#messages
|
||||
object Counter {
|
||||
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 {
|
||||
case class Store(entry: Entry)
|
||||
case class Get(key: String)
|
||||
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
|
||||
150
akka-docs/scala/code/docs/actor/FaultHandlingDocSpec.scala
Normal file
150
akka-docs/scala/code/docs/actor/FaultHandlingDocSpec.scala
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package docs.actor
|
||||
|
||||
//#testkit
|
||||
import akka.testkit.{ AkkaSpec, ImplicitSender, EventFilter }
|
||||
import akka.actor.{ ActorRef, Props, Terminated }
|
||||
|
||||
//#testkit
|
||||
object FaultHandlingDocSpec {
|
||||
//#supervisor
|
||||
//#child
|
||||
import akka.actor.Actor
|
||||
|
||||
//#child
|
||||
//#supervisor
|
||||
//#supervisor
|
||||
class Supervisor extends Actor {
|
||||
//#strategy
|
||||
import akka.actor.OneForOneStrategy
|
||||
import akka.actor.SupervisorStrategy._
|
||||
import akka.util.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 akka.util.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
|
||||
|
||||
//#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
|
||||
}
|
||||
|
||||
//#testkit
|
||||
class FaultHandlingDocSpec extends AkkaSpec with ImplicitSender {
|
||||
//#testkit
|
||||
|
||||
import FaultHandlingDocSpec._
|
||||
//#testkit
|
||||
|
||||
"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[ArithmeticException](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
|
||||
expectMsg(Terminated(child))
|
||||
child.isTerminated must be(true)
|
||||
//#stop
|
||||
}
|
||||
EventFilter[Exception]("CRASH", occurrences = 4) 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
|
||||
expectMsg(Terminated(child2))
|
||||
//#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
|
||||
56
akka-docs/scala/code/docs/actor/SchedulerDocSpec.scala
Normal file
56
akka-docs/scala/code/docs/actor/SchedulerDocSpec.scala
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package docs.actor
|
||||
|
||||
//#imports1
|
||||
import akka.actor.Actor
|
||||
import akka.actor.Props
|
||||
import akka.util.duration._
|
||||
|
||||
//#imports1
|
||||
|
||||
import org.scalatest.{ BeforeAndAfterAll, WordSpec }
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import akka.testkit._
|
||||
|
||||
class SchedulerDocSpec extends AkkaSpec(Map("akka.loglevel" -> "INFO")) {
|
||||
"schedule a one-off task" in {
|
||||
//#schedule-one-off-message
|
||||
//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 the current time) to the testActor after 50ms
|
||||
system.scheduler.scheduleOnce(50 milliseconds) {
|
||||
testActor ! System.currentTimeMillis
|
||||
}
|
||||
//#schedule-one-off-thunk
|
||||
|
||||
}
|
||||
|
||||
"schedule a recurring task" in {
|
||||
//#schedule-recurring
|
||||
val Tick = "tick"
|
||||
val tickActor = system.actorOf(Props(new Actor {
|
||||
def receive = {
|
||||
case Tick ⇒ //Do something
|
||||
}
|
||||
}))
|
||||
//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)
|
||||
}
|
||||
}
|
||||
165
akka-docs/scala/code/docs/actor/TypedActorDocSpec.scala
Normal file
165
akka-docs/scala/code/docs/actor/TypedActorDocSpec.scala
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package docs.actor
|
||||
|
||||
//#imports
|
||||
import akka.dispatch.{ Promise, Future, Await }
|
||||
import akka.util.duration._
|
||||
import akka.actor.{ ActorContext, TypedActor, TypedProps }
|
||||
|
||||
//#imports
|
||||
|
||||
import org.scalatest.{ BeforeAndAfterAll, WordSpec }
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import akka.testkit._
|
||||
|
||||
//#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
|
||||
//#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
|
||||
import TypedActor.dispatcher //So we can create Promises
|
||||
|
||||
def squareDontCare(i: Int): Unit = i * i //Nobody cares :(
|
||||
|
||||
def square(i: Int): Future[Int] = Promise successful i * i
|
||||
|
||||
def squareNowPlease(i: Int): Option[Int] = Some(i * i)
|
||||
|
||||
def squareNow(i: Int): Int = i * i
|
||||
//#typed-actor-impl-methods
|
||||
}
|
||||
//#typed-actor-impl
|
||||
import java.lang.String.{ valueOf ⇒ println } //Mr funny man avoids printing to stdout AND keeping docs alright
|
||||
//#typed-actor-supercharge
|
||||
trait Foo {
|
||||
def doFoo(times: Int): Unit = println("doFoo(" + times + ")")
|
||||
}
|
||||
|
||||
trait Bar {
|
||||
import TypedActor.dispatcher //So we have an implicit dispatcher for our Promise
|
||||
def doBar(str: String): Future[String] = Promise successful str.toUpperCase
|
||||
}
|
||||
|
||||
class FooBar extends Foo with Bar
|
||||
//#typed-actor-supercharge
|
||||
|
||||
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) must be === 100
|
||||
|
||||
oSquare must be === Some(100)
|
||||
|
||||
iSquare must 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 {
|
||||
//#typed-actor-remote
|
||||
val typedActor: Foo with Bar =
|
||||
TypedActor(system).
|
||||
typedActorOf(
|
||||
TypedProps[FooBar],
|
||||
system.actorFor("akka://SomeSystem@somehost:2552/user/some/foobar"))
|
||||
//Use "typedActor" as a FooBar
|
||||
//#typed-actor-remote
|
||||
}
|
||||
|
||||
"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) must be === "YES"
|
||||
}
|
||||
}
|
||||
50
akka-docs/scala/code/docs/actor/UnnestedReceives.scala
Normal file
50
akka-docs/scala/code/docs/actor/UnnestedReceives.scala
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.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