2012-01-23 15:37:43 +01:00
|
|
|
/**
|
2015-03-07 22:58:48 -08:00
|
|
|
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
|
2012-01-23 15:37:43 +01:00
|
|
|
*/
|
2012-05-22 11:37:09 +02:00
|
|
|
package docs.actor
|
2012-01-23 15:37:43 +01:00
|
|
|
|
2012-06-28 15:33:49 +02:00
|
|
|
import language.postfixOps
|
|
|
|
|
|
2013-12-03 16:34:26 +01:00
|
|
|
import akka.testkit.{ AkkaSpec => MyFavoriteTestFrameWorkPlusAkkaTestKit }
|
2012-12-26 17:00:58 +01:00
|
|
|
import akka.util.ByteString
|
|
|
|
|
|
2012-01-23 17:43:30 +01:00
|
|
|
//#test-code
|
|
|
|
|
import akka.actor.Props
|
2012-10-30 15:08:41 +01:00
|
|
|
import scala.collection.immutable
|
2012-01-23 15:37:43 +01:00
|
|
|
|
2015-01-30 18:34:03 +01:00
|
|
|
object FSMDocSpec {
|
|
|
|
|
// messages and data types
|
|
|
|
|
//#test-code
|
|
|
|
|
import akka.actor.ActorRef
|
2013-04-14 22:56:41 +02:00
|
|
|
//#simple-events
|
|
|
|
|
// received events
|
2014-03-07 13:20:01 +01:00
|
|
|
final case class SetTarget(ref: ActorRef)
|
|
|
|
|
final case class Queue(obj: Any)
|
2013-04-14 22:56:41 +02:00
|
|
|
case object Flush
|
|
|
|
|
|
|
|
|
|
// sent events
|
2014-03-07 13:20:01 +01:00
|
|
|
final case class Batch(obj: immutable.Seq[Any])
|
2013-04-14 22:56:41 +02:00
|
|
|
//#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
|
2014-03-07 13:20:01 +01:00
|
|
|
final case class Todo(target: ActorRef, queue: immutable.Seq[Any]) extends Data
|
2013-04-14 22:56:41 +02:00
|
|
|
//#simple-state
|
2015-01-30 18:34:03 +01:00
|
|
|
//#test-code
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class FSMDocSpec extends MyFavoriteTestFrameWorkPlusAkkaTestKit {
|
|
|
|
|
import FSMDocSpec._
|
|
|
|
|
|
|
|
|
|
//#fsm-code-elided
|
|
|
|
|
//#simple-imports
|
|
|
|
|
import akka.actor.{ ActorRef, FSM }
|
|
|
|
|
import scala.concurrent.duration._
|
|
|
|
|
//#simple-imports
|
2013-04-14 22:56:41 +02:00
|
|
|
//#simple-fsm
|
2014-11-03 23:43:50 +01:00
|
|
|
class Buncher extends FSM[State, Data] {
|
2013-04-14 22:56:41 +02:00
|
|
|
|
|
|
|
|
//#fsm-body
|
|
|
|
|
startWith(Idle, Uninitialized)
|
|
|
|
|
|
|
|
|
|
//#when-syntax
|
|
|
|
|
when(Idle) {
|
2013-12-03 16:34:26 +01:00
|
|
|
case Event(SetTarget(ref), Uninitialized) =>
|
2013-04-14 22:56:41 +02:00
|
|
|
stay using Todo(ref, Vector.empty)
|
|
|
|
|
}
|
|
|
|
|
//#when-syntax
|
2012-01-23 15:37:43 +01:00
|
|
|
|
2013-04-14 22:56:41 +02:00
|
|
|
//#transition-elided
|
|
|
|
|
onTransition {
|
2013-12-03 16:34:26 +01:00
|
|
|
case Active -> Idle =>
|
2013-04-14 22:56:41 +02:00
|
|
|
stateData match {
|
2013-12-03 16:34:26 +01:00
|
|
|
case Todo(ref, queue) => ref ! Batch(queue)
|
2015-01-30 18:34:03 +01:00
|
|
|
case _ => // nothing to do
|
2013-04-14 22:56:41 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
//#transition-elided
|
|
|
|
|
//#when-syntax
|
2012-01-23 15:37:43 +01:00
|
|
|
|
2013-04-14 22:56:41 +02:00
|
|
|
when(Active, stateTimeout = 1 second) {
|
2013-12-03 16:34:26 +01:00
|
|
|
case Event(Flush | StateTimeout, t: Todo) =>
|
2013-04-14 22:56:41 +02:00
|
|
|
goto(Idle) using t.copy(queue = Vector.empty)
|
|
|
|
|
}
|
|
|
|
|
//#when-syntax
|
2012-01-23 15:37:43 +01:00
|
|
|
|
2013-04-14 22:56:41 +02:00
|
|
|
//#unhandled-elided
|
|
|
|
|
whenUnhandled {
|
|
|
|
|
// common code for both states
|
2013-12-03 16:34:26 +01:00
|
|
|
case Event(Queue(obj), t @ Todo(_, v)) =>
|
2013-04-14 22:56:41 +02:00
|
|
|
goto(Active) using t.copy(queue = v :+ obj)
|
2012-01-23 15:37:43 +01:00
|
|
|
|
2013-12-03 16:34:26 +01:00
|
|
|
case Event(e, s) =>
|
2013-04-14 22:56:41 +02:00
|
|
|
log.warning("received unhandled request {} in state {}/{}", e, stateName, s)
|
|
|
|
|
stay
|
2012-01-23 15:37:43 +01:00
|
|
|
}
|
2013-04-14 22:56:41 +02:00
|
|
|
//#unhandled-elided
|
|
|
|
|
//#fsm-body
|
2012-05-07 17:52:14 +02:00
|
|
|
|
2013-04-14 22:56:41 +02:00
|
|
|
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
|
|
|
|
|
|
2014-11-03 23:43:50 +01:00
|
|
|
class Dummy extends FSM[StateType, Int] {
|
2013-04-14 22:56:41 +02:00
|
|
|
class X
|
|
|
|
|
val newData = 42
|
|
|
|
|
object WillDo
|
|
|
|
|
object Tick
|
|
|
|
|
|
|
|
|
|
//#modifier-syntax
|
|
|
|
|
when(SomeState) {
|
2013-12-03 16:34:26 +01:00
|
|
|
case Event(msg, _) =>
|
2013-04-14 22:56:41 +02:00
|
|
|
goto(Processing) using (newData) forMax (5 seconds) replying (WillDo)
|
|
|
|
|
}
|
|
|
|
|
//#modifier-syntax
|
2012-05-07 17:52:14 +02:00
|
|
|
|
2013-04-14 22:56:41 +02:00
|
|
|
//#transition-syntax
|
|
|
|
|
onTransition {
|
2015-01-13 22:01:06 -08:00
|
|
|
case Idle -> Active => setTimer("timeout", Tick, 1 second, repeat = true)
|
2013-12-03 16:34:26 +01:00
|
|
|
case Active -> _ => cancelTimer("timeout")
|
|
|
|
|
case x -> Idle => log.info("entering Idle from " + x)
|
2013-04-14 22:56:41 +02:00
|
|
|
}
|
|
|
|
|
//#transition-syntax
|
2012-05-07 17:52:14 +02:00
|
|
|
|
2013-04-14 22:56:41 +02:00
|
|
|
//#alt-transition-syntax
|
|
|
|
|
onTransition(handler _)
|
2012-05-07 17:52:14 +02:00
|
|
|
|
2013-04-14 22:56:41 +02:00
|
|
|
def handler(from: StateType, to: StateType) {
|
|
|
|
|
// handle it here ...
|
|
|
|
|
}
|
|
|
|
|
//#alt-transition-syntax
|
2012-05-07 17:52:14 +02:00
|
|
|
|
2013-04-14 22:56:41 +02:00
|
|
|
//#stop-syntax
|
|
|
|
|
when(Error) {
|
2013-12-03 16:34:26 +01:00
|
|
|
case Event("stop", _) =>
|
2013-04-14 22:56:41 +02:00
|
|
|
// do cleanup ...
|
|
|
|
|
stop()
|
|
|
|
|
}
|
|
|
|
|
//#stop-syntax
|
|
|
|
|
|
|
|
|
|
//#transform-syntax
|
|
|
|
|
when(SomeState)(transform {
|
2013-12-03 16:34:26 +01:00
|
|
|
case Event(bytes: ByteString, read) => stay using (read + bytes.length)
|
2013-04-14 22:56:41 +02:00
|
|
|
} using {
|
2013-12-03 16:34:26 +01:00
|
|
|
case s @ FSM.State(state, read, timeout, stopReason, replies) if read > 1000 =>
|
2013-04-14 22:56:41 +02:00
|
|
|
goto(Processing)
|
|
|
|
|
})
|
|
|
|
|
//#transform-syntax
|
|
|
|
|
|
|
|
|
|
//#alt-transform-syntax
|
|
|
|
|
val processingTrigger: PartialFunction[State, State] = {
|
2013-12-03 16:34:26 +01:00
|
|
|
case s @ FSM.State(state, read, timeout, stopReason, replies) if read > 1000 =>
|
2013-04-14 22:56:41 +02:00
|
|
|
goto(Processing)
|
|
|
|
|
}
|
2012-05-07 17:52:14 +02:00
|
|
|
|
2013-04-14 22:56:41 +02:00
|
|
|
when(SomeState)(transform {
|
2013-12-03 16:34:26 +01:00
|
|
|
case Event(bytes: ByteString, read) => stay using (read + bytes.length)
|
2013-04-14 22:56:41 +02:00
|
|
|
} using processingTrigger)
|
|
|
|
|
//#alt-transform-syntax
|
2012-05-07 17:52:14 +02:00
|
|
|
|
2013-04-14 22:56:41 +02:00
|
|
|
//#termination-syntax
|
|
|
|
|
onTermination {
|
2013-12-03 16:34:26 +01:00
|
|
|
case StopEvent(FSM.Normal, state, data) => // ...
|
|
|
|
|
case StopEvent(FSM.Shutdown, state, data) => // ...
|
|
|
|
|
case StopEvent(FSM.Failure(cause), state, data) => // ...
|
2012-05-07 17:52:14 +02:00
|
|
|
}
|
2013-04-14 22:56:41 +02:00
|
|
|
//#termination-syntax
|
2012-05-07 17:52:14 +02:00
|
|
|
|
2013-04-14 22:56:41 +02:00
|
|
|
//#unhandled-syntax
|
|
|
|
|
whenUnhandled {
|
2013-12-03 16:34:26 +01:00
|
|
|
case Event(x: X, data) =>
|
2013-04-14 22:56:41 +02:00
|
|
|
log.info("Received unhandled event: " + x)
|
|
|
|
|
stay
|
2013-12-03 16:34:26 +01:00
|
|
|
case Event(msg, _) =>
|
2013-04-14 22:56:41 +02:00
|
|
|
log.warning("Received unknown event: " + msg)
|
|
|
|
|
goto(Error)
|
2012-05-07 17:52:14 +02:00
|
|
|
}
|
2013-04-14 22:56:41 +02:00
|
|
|
//#unhandled-syntax
|
2012-05-07 17:52:14 +02:00
|
|
|
|
|
|
|
|
}
|
2013-04-14 22:56:41 +02:00
|
|
|
|
|
|
|
|
//#logging-fsm
|
|
|
|
|
import akka.actor.LoggingFSM
|
2014-11-03 23:43:50 +01:00
|
|
|
class MyFSM extends LoggingFSM[StateType, Data] {
|
2013-04-14 22:56:41 +02:00
|
|
|
//#body-elided
|
|
|
|
|
override def logDepth = 12
|
|
|
|
|
onTermination {
|
2013-12-03 16:34:26 +01:00
|
|
|
case StopEvent(FSM.Failure(_), state, data) =>
|
2013-04-14 22:56:41 +02:00
|
|
|
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 {
|
2012-01-23 15:37:43 +01:00
|
|
|
|
2012-11-28 15:43:42 +01:00
|
|
|
"demonstrate NullFunction" in {
|
2014-11-03 23:43:50 +01:00
|
|
|
class A extends FSM[Int, Null] {
|
2012-11-28 15:43:42 +01:00
|
|
|
val SomeState = 0
|
|
|
|
|
//#NullFunction
|
|
|
|
|
when(SomeState)(FSM.NullFunction)
|
|
|
|
|
//#NullFunction
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-01-23 15:37:43 +01:00
|
|
|
"batch correctly" in {
|
2013-04-14 22:56:41 +02:00
|
|
|
val buncher = system.actorOf(Props(classOf[Buncher], this))
|
2012-01-23 15:37:43 +01:00
|
|
|
buncher ! SetTarget(testActor)
|
|
|
|
|
buncher ! Queue(42)
|
|
|
|
|
buncher ! Queue(43)
|
2012-10-30 15:08:41 +01:00
|
|
|
expectMsg(Batch(immutable.Seq(42, 43)))
|
2012-01-23 15:37:43 +01:00
|
|
|
buncher ! Queue(44)
|
|
|
|
|
buncher ! Flush
|
|
|
|
|
buncher ! Queue(45)
|
2012-10-30 15:08:41 +01:00
|
|
|
expectMsg(Batch(immutable.Seq(44)))
|
|
|
|
|
expectMsg(Batch(immutable.Seq(45)))
|
2012-01-23 15:37:43 +01:00
|
|
|
}
|
|
|
|
|
|
2012-12-26 17:00:58 +01:00
|
|
|
"not batch if uninitialized" in {
|
2013-04-14 22:56:41 +02:00
|
|
|
val buncher = system.actorOf(Props(classOf[Buncher], this))
|
2012-01-23 15:37:43 +01:00
|
|
|
buncher ! Queue(42)
|
|
|
|
|
expectNoMsg
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-01-23 17:43:30 +01:00
|
|
|
}
|
|
|
|
|
//#test-code
|