Merge branch 'wip-FSM-∂π'
This commit is contained in:
commit
26925687f9
4 changed files with 211 additions and 109 deletions
|
|
@ -262,6 +262,25 @@ class FSMActorSpec extends AkkaSpec(Map("akka.actor.debug.fsm" -> true)) with Im
|
||||||
expectMsg(1 second, IndexedSeq(LogEntry(1, 1, "log"), LogEntry(1, 1, "count"), LogEntry(1, 2, "log")))
|
expectMsg(1 second, IndexedSeq(LogEntry(1, 1, "log"), LogEntry(1, 1, "count"), LogEntry(1, 2, "log")))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"allow transforming of state results" in {
|
||||||
|
import akka.actor.FSM._
|
||||||
|
val fsmref = system.actorOf(Props(new Actor with FSM[Int, Int] {
|
||||||
|
startWith(0, 0)
|
||||||
|
when(0)(transform {
|
||||||
|
case Event("go", _) ⇒ stay
|
||||||
|
} using {
|
||||||
|
case x ⇒ goto(1)
|
||||||
|
})
|
||||||
|
when(1) {
|
||||||
|
case _ ⇒ stay
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
fsmref ! SubscribeTransitionCallBack(testActor)
|
||||||
|
fsmref ! "go"
|
||||||
|
expectMsg(CurrentState(fsmref, 0))
|
||||||
|
expectMsg(Transition(fsmref, 0, 1))
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,7 @@ object FSM {
|
||||||
* timerActive_? ("tock")
|
* timerActive_? ("tock")
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
trait FSM[S, D] extends Listeners {
|
trait FSM[S, D] extends Listeners with ActorLogging {
|
||||||
this: Actor ⇒
|
this: Actor ⇒
|
||||||
|
|
||||||
import FSM._
|
import FSM._
|
||||||
|
|
@ -186,8 +186,6 @@ trait FSM[S, D] extends Listeners {
|
||||||
val -> = FSM.->
|
val -> = FSM.->
|
||||||
val StateTimeout = FSM.StateTimeout
|
val StateTimeout = FSM.StateTimeout
|
||||||
|
|
||||||
val log = Logging(context.system, this)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ****************************************
|
* ****************************************
|
||||||
* DSL
|
* DSL
|
||||||
|
|
@ -255,6 +253,13 @@ trait FSM[S, D] extends Listeners {
|
||||||
*/
|
*/
|
||||||
protected final def stop(reason: Reason, stateData: D): State = stay using stateData withStopReason (reason)
|
protected final def stop(reason: Reason, stateData: D): State = stay using stateData withStopReason (reason)
|
||||||
|
|
||||||
|
protected final class TransformHelper(func: StateFunction) {
|
||||||
|
def using(andThen: PartialFunction[State, State]): StateFunction =
|
||||||
|
func andThen (andThen orElse { case x ⇒ x })
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final def transform(func: StateFunction): TransformHelper = new TransformHelper(func)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Schedule named timer to deliver message after given delay, possibly repeating.
|
* Schedule named timer to deliver message after given delay, possibly repeating.
|
||||||
* @param name identifier to be used with cancelTimer()
|
* @param name identifier to be used with cancelTimer()
|
||||||
|
|
@ -327,7 +332,7 @@ trait FSM[S, D] extends Listeners {
|
||||||
* Convenience wrapper for using a total function instead of a partial
|
* Convenience wrapper for using a total function instead of a partial
|
||||||
* function literal. To be used with onTransition.
|
* function literal. To be used with onTransition.
|
||||||
*/
|
*/
|
||||||
implicit protected final def total2pf(transitionHandler: (S, S) ⇒ Unit) =
|
implicit protected final def total2pf(transitionHandler: (S, S) ⇒ Unit): TransitionHandler =
|
||||||
new TransitionHandler {
|
new TransitionHandler {
|
||||||
def isDefinedAt(in: (S, S)) = true
|
def isDefinedAt(in: (S, S)) = true
|
||||||
def apply(in: (S, S)) { transitionHandler(in._1, in._2) }
|
def apply(in: (S, S)) { transitionHandler(in._1, in._2) }
|
||||||
|
|
@ -336,7 +341,7 @@ trait FSM[S, D] extends Listeners {
|
||||||
/**
|
/**
|
||||||
* Set handler which is called upon termination of this FSM actor.
|
* Set handler which is called upon termination of this FSM actor.
|
||||||
*/
|
*/
|
||||||
protected final def onTermination(terminationHandler: PartialFunction[StopEvent[S, D], Unit]): Unit =
|
protected final def onTermination(terminationHandler: PartialFunction[StopEvent, Unit]): Unit =
|
||||||
terminateEvent = terminationHandler
|
terminateEvent = terminationHandler
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -415,7 +420,7 @@ trait FSM[S, D] extends Listeners {
|
||||||
/*
|
/*
|
||||||
* termination handling
|
* termination handling
|
||||||
*/
|
*/
|
||||||
private var terminateEvent: PartialFunction[StopEvent[S, D], Unit] = NullFunction
|
private var terminateEvent: PartialFunction[StopEvent, Unit] = NullFunction
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* transition handling
|
* transition handling
|
||||||
|
|
@ -538,7 +543,7 @@ trait FSM[S, D] extends Listeners {
|
||||||
|
|
||||||
case class Event(event: Any, stateData: D)
|
case class Event(event: Any, stateData: D)
|
||||||
|
|
||||||
case class StopEvent[S, D](reason: Reason, currentState: S, stateData: D)
|
case class StopEvent(reason: Reason, currentState: S, stateData: D)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -37,11 +37,15 @@ class FSMDocSpec extends AkkaSpec {
|
||||||
//#simple-fsm
|
//#simple-fsm
|
||||||
class Buncher extends Actor with FSM[State, Data] {
|
class Buncher extends Actor with FSM[State, Data] {
|
||||||
|
|
||||||
|
//#fsm-body
|
||||||
startWith(Idle, Uninitialized)
|
startWith(Idle, Uninitialized)
|
||||||
|
|
||||||
|
//#when-syntax
|
||||||
when(Idle) {
|
when(Idle) {
|
||||||
case Event(SetTarget(ref), Uninitialized) ⇒ stay using Todo(ref, Vector.empty)
|
case Event(SetTarget(ref), Uninitialized) ⇒
|
||||||
|
stay using Todo(ref, Vector.empty)
|
||||||
}
|
}
|
||||||
|
//#when-syntax
|
||||||
|
|
||||||
//#transition-elided
|
//#transition-elided
|
||||||
onTransition {
|
onTransition {
|
||||||
|
|
@ -51,10 +55,13 @@ class FSMDocSpec extends AkkaSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//#transition-elided
|
//#transition-elided
|
||||||
|
//#when-syntax
|
||||||
|
|
||||||
when(Active, stateTimeout = 1 second) {
|
when(Active, stateTimeout = 1 second) {
|
||||||
case Event(Flush | FSM.StateTimeout, t: Todo) ⇒ goto(Idle) using t.copy(queue = Vector.empty)
|
case Event(Flush | StateTimeout, t: Todo) ⇒
|
||||||
|
goto(Idle) using t.copy(queue = Vector.empty)
|
||||||
}
|
}
|
||||||
|
//#when-syntax
|
||||||
|
|
||||||
//#unhandled-elided
|
//#unhandled-elided
|
||||||
whenUnhandled {
|
whenUnhandled {
|
||||||
|
|
@ -67,10 +74,116 @@ class FSMDocSpec extends AkkaSpec {
|
||||||
stay
|
stay
|
||||||
}
|
}
|
||||||
//#unhandled-elided
|
//#unhandled-elided
|
||||||
|
//#fsm-body
|
||||||
|
|
||||||
initialize
|
initialize
|
||||||
}
|
}
|
||||||
//#simple-fsm
|
//#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
|
//#fsm-code-elided
|
||||||
|
|
||||||
"batch correctly" in {
|
"batch correctly" in {
|
||||||
|
|
|
||||||
|
|
@ -118,19 +118,11 @@ The FSM Trait and Object
|
||||||
|
|
||||||
The :class:`FSM` trait may only be mixed into an :class:`Actor`. Instead of
|
The :class:`FSM` trait may only be mixed into an :class:`Actor`. Instead of
|
||||||
extending :class:`Actor`, the self type approach was chosen in order to make it
|
extending :class:`Actor`, the self type approach was chosen in order to make it
|
||||||
obvious that an actor is actually created. Importing all members of the
|
obvious that an actor is actually created:
|
||||||
:obj:`FSM` object is recommended if you want to directly access the symbols
|
|
||||||
like :obj:`StateTimeout`. This import is usually placed inside the state
|
|
||||||
machine definition:
|
|
||||||
|
|
||||||
.. code-block:: scala
|
.. includecode:: code/akka/docs/actor/FSMDocSpec.scala
|
||||||
|
:include: simple-fsm
|
||||||
class MyFSM extends Actor with FSM[State, Data] {
|
:exclude: fsm-body
|
||||||
import FSM._
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
The :class:`FSM` trait takes two type parameters:
|
The :class:`FSM` trait takes two type parameters:
|
||||||
|
|
||||||
|
|
@ -153,7 +145,7 @@ Defining States
|
||||||
|
|
||||||
A state is defined by one or more invocations of the method
|
A state is defined by one or more invocations of the method
|
||||||
|
|
||||||
:func:`when(<name>[, stateTimeout = <timeout>])(stateFunction)`.
|
:func:`when(<name>[, stateTimeout = <timeout>])(stateFunction)`.
|
||||||
|
|
||||||
The given name must be an object which is type-compatible with the first type
|
The given name must be an object which is type-compatible with the first type
|
||||||
parameter given to the :class:`FSM` trait. This object is used as a hash key,
|
parameter given to the :class:`FSM` trait. This object is used as a hash key,
|
||||||
|
|
@ -165,27 +157,18 @@ If the :meth:`stateTimeout` parameter is given, then all transitions into this
|
||||||
state, including staying, receive this timeout by default. Initiating the
|
state, including staying, receive this timeout by default. Initiating the
|
||||||
transition with an explicit timeout may be used to override this default, see
|
transition with an explicit timeout may be used to override this default, see
|
||||||
`Initiating Transitions`_ for more information. The state timeout of any state
|
`Initiating Transitions`_ for more information. The state timeout of any state
|
||||||
may be changed during action processing with :func:`setStateTimeout(state,
|
may be changed during action processing with
|
||||||
duration)`. This enables runtime configuration e.g. via external message.
|
:func:`setStateTimeout(state, duration)`. This enables runtime configuration
|
||||||
|
e.g. via external message.
|
||||||
|
|
||||||
The :meth:`stateFunction` argument is a :class:`PartialFunction[Event, State]`,
|
The :meth:`stateFunction` argument is a :class:`PartialFunction[Event, State]`,
|
||||||
which is conveniently given using the partial function literal syntax as
|
which is conveniently given using the partial function literal syntax as
|
||||||
demonstrated below:
|
demonstrated below:
|
||||||
|
|
||||||
.. code-block:: scala
|
.. includecode:: code/akka/docs/actor/FSMDocSpec.scala
|
||||||
|
:include: when-syntax
|
||||||
|
|
||||||
when(Idle) {
|
The :class:`Event(msg: Any, data: D)` case class is parameterized with the data
|
||||||
case Event(Start(msg), _) =>
|
|
||||||
goto(Timer) using (msg, sender)
|
|
||||||
}
|
|
||||||
|
|
||||||
when(Timer, stateTimeout = 12 seconds) {
|
|
||||||
case Event(StateTimeout, (msg, sender)) =>
|
|
||||||
sender ! msg
|
|
||||||
goto(Idle)
|
|
||||||
}
|
|
||||||
|
|
||||||
The :class:`Event(msg: Any, data: D)` case class is parameterized with the data
|
|
||||||
type held by the FSM for convenient pattern matching.
|
type held by the FSM for convenient pattern matching.
|
||||||
|
|
||||||
Defining the Initial State
|
Defining the Initial State
|
||||||
|
|
@ -193,7 +176,7 @@ Defining the Initial State
|
||||||
|
|
||||||
Each FSM needs a starting point, which is declared using
|
Each FSM needs a starting point, which is declared using
|
||||||
|
|
||||||
:func:`startWith(state, data[, timeout])`
|
:func:`startWith(state, data[, timeout])`
|
||||||
|
|
||||||
The optionally given timeout argument overrides any specification given for the
|
The optionally given timeout argument overrides any specification given for the
|
||||||
desired initial state. If you want to cancel a default timeout, use
|
desired initial state. If you want to cancel a default timeout, use
|
||||||
|
|
@ -206,16 +189,8 @@ If a state doesn't handle a received event a warning is logged. If you want to
|
||||||
do something else in this case you can specify that with
|
do something else in this case you can specify that with
|
||||||
:func:`whenUnhandled(stateFunction)`:
|
:func:`whenUnhandled(stateFunction)`:
|
||||||
|
|
||||||
.. code-block:: scala
|
.. includecode:: code/akka/docs/actor/FSMDocSpec.scala
|
||||||
|
:include: unhandled-syntax
|
||||||
whenUnhandled {
|
|
||||||
case Event(x : X, data) =>
|
|
||||||
log.info(this, "Received unhandled event: " + x)
|
|
||||||
stay
|
|
||||||
case Event(msg, _) =>
|
|
||||||
log.warn(this, "Received unknown event: " + x)
|
|
||||||
goto(Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
**IMPORTANT**: This handler is not stacked, meaning that each invocation of
|
**IMPORTANT**: This handler is not stacked, meaning that each invocation of
|
||||||
:func:`whenUnhandled` replaces the previously installed handler.
|
:func:`whenUnhandled` replaces the previously installed handler.
|
||||||
|
|
@ -230,7 +205,8 @@ The state definition can either be the current state, as described by the
|
||||||
:func:`goto(state)`. The resulting object allows further qualification by way
|
:func:`goto(state)`. The resulting object allows further qualification by way
|
||||||
of the modifiers described in the following:
|
of the modifiers described in the following:
|
||||||
|
|
||||||
:meth:`forMax(duration)`
|
* :meth:`forMax(duration)`
|
||||||
|
|
||||||
This modifier sets a state timeout on the next state. This means that a timer
|
This modifier sets a state timeout on the next state. This means that a timer
|
||||||
is started which upon expiry sends a :obj:`StateTimeout` message to the FSM.
|
is started which upon expiry sends a :obj:`StateTimeout` message to the FSM.
|
||||||
This timer is canceled upon reception of any other message in the meantime;
|
This timer is canceled upon reception of any other message in the meantime;
|
||||||
|
|
@ -241,23 +217,21 @@ of the modifiers described in the following:
|
||||||
specified for the target state. If you want to cancel the default timeout,
|
specified for the target state. If you want to cancel the default timeout,
|
||||||
use :obj:`Duration.Inf`.
|
use :obj:`Duration.Inf`.
|
||||||
|
|
||||||
:meth:`using(data)`
|
* :meth:`using(data)`
|
||||||
|
|
||||||
This modifier replaces the old state data with the new data given. If you
|
This modifier replaces the old state data with the new data given. If you
|
||||||
follow the advice :ref:`above <fsm-philosophy>`, this is the only place where
|
follow the advice :ref:`above <fsm-philosophy>`, this is the only place where
|
||||||
internal state data are ever modified.
|
internal state data are ever modified.
|
||||||
|
|
||||||
:meth:`replying(msg)`
|
* :meth:`replying(msg)`
|
||||||
|
|
||||||
This modifier sends a reply to the currently processed message and otherwise
|
This modifier sends a reply to the currently processed message and otherwise
|
||||||
does not modify the state transition.
|
does not modify the state transition.
|
||||||
|
|
||||||
All modifier can be chained to achieve a nice and concise description:
|
All modifier can be chained to achieve a nice and concise description:
|
||||||
|
|
||||||
.. code-block:: scala
|
.. includecode:: code/akka/docs/actor/FSMDocSpec.scala
|
||||||
|
:include: modifier-syntax
|
||||||
when(State) {
|
|
||||||
case Event(msg, _) =>
|
|
||||||
goto(Processing) using (msg) forMax (5 seconds) replying (WillDo)
|
|
||||||
}
|
|
||||||
|
|
||||||
The parentheses are not actually needed in all cases, but they visually
|
The parentheses are not actually needed in all cases, but they visually
|
||||||
distinguish between modifiers and their arguments and therefore make the code
|
distinguish between modifiers and their arguments and therefore make the code
|
||||||
|
|
@ -267,7 +241,7 @@ even more pleasant to read for foreigners.
|
||||||
|
|
||||||
Please note that the ``return`` statement may not be used in :meth:`when`
|
Please note that the ``return`` statement may not be used in :meth:`when`
|
||||||
blocks or similar; this is a Scala restriction. Either refactor your code
|
blocks or similar; this is a Scala restriction. Either refactor your code
|
||||||
using ``if () ... else ...`` or move it into a method definition.
|
using ``if () ... else ...`` or move it into a method definition.
|
||||||
|
|
||||||
Monitoring Transitions
|
Monitoring Transitions
|
||||||
----------------------
|
----------------------
|
||||||
|
|
@ -293,13 +267,8 @@ The handler is a partial function which takes a pair of states as input; no
|
||||||
resulting state is needed as it is not possible to modify the transition in
|
resulting state is needed as it is not possible to modify the transition in
|
||||||
progress.
|
progress.
|
||||||
|
|
||||||
.. code-block:: scala
|
.. includecode:: code/akka/docs/actor/FSMDocSpec.scala
|
||||||
|
:include: transition-syntax
|
||||||
onTransition {
|
|
||||||
case Idle -> Active => setTimer("timeout")
|
|
||||||
case Active -> _ => cancelTimer("timeout")
|
|
||||||
case x -> Idle => log.info("entering Idle from "+x)
|
|
||||||
}
|
|
||||||
|
|
||||||
The convenience extractor :obj:`->` enables decomposition of the pair of states
|
The convenience extractor :obj:`->` enables decomposition of the pair of states
|
||||||
with a clear visual reminder of the transition's direction. As usual in pattern
|
with a clear visual reminder of the transition's direction. As usual in pattern
|
||||||
|
|
@ -311,13 +280,8 @@ It is also possible to pass a function object accepting two states to
|
||||||
:func:`onTransition`, in case your transition handling logic is implemented as
|
:func:`onTransition`, in case your transition handling logic is implemented as
|
||||||
a method:
|
a method:
|
||||||
|
|
||||||
.. code-block:: scala
|
.. includecode:: code/akka/docs/actor/FSMDocSpec.scala
|
||||||
|
:include: alt-transition-syntax
|
||||||
onTransition(handler _)
|
|
||||||
|
|
||||||
private def handler(from: State, to: State) {
|
|
||||||
...
|
|
||||||
}
|
|
||||||
|
|
||||||
The handlers registered with this method are stacked, so you can intersperse
|
The handlers registered with this method are stacked, so you can intersperse
|
||||||
:func:`onTransition` blocks with :func:`when` blocks as suits your design. It
|
:func:`onTransition` blocks with :func:`when` blocks as suits your design. It
|
||||||
|
|
@ -338,8 +302,8 @@ External Monitoring
|
||||||
|
|
||||||
External actors may be registered to be notified of state transitions by
|
External actors may be registered to be notified of state transitions by
|
||||||
sending a message :class:`SubscribeTransitionCallBack(actorRef)`. The named
|
sending a message :class:`SubscribeTransitionCallBack(actorRef)`. The named
|
||||||
actor will be sent a :class:`CurrentState(self, stateName)` message immediately
|
actor will be sent a :class:`CurrentState(self, stateName)` message immediately
|
||||||
and will receive :class:`Transition(actorRef, oldState, newState)` messages
|
and will receive :class:`Transition(actorRef, oldState, newState)` messages
|
||||||
whenever a new state is reached. External monitors may be unregistered by
|
whenever a new state is reached. External monitors may be unregistered by
|
||||||
sending :class:`UnsubscribeTransitionCallBack(actorRef)` to the FSM actor.
|
sending :class:`UnsubscribeTransitionCallBack(actorRef)` to the FSM actor.
|
||||||
|
|
||||||
|
|
@ -347,13 +311,31 @@ Registering a not-running listener generates a warning and fails gracefully.
|
||||||
Stopping a listener without unregistering will remove the listener from the
|
Stopping a listener without unregistering will remove the listener from the
|
||||||
subscription list upon the next transition.
|
subscription list upon the next transition.
|
||||||
|
|
||||||
|
Transforming State
|
||||||
|
------------------
|
||||||
|
|
||||||
|
The partial functions supplied as argument to the ``when()`` blocks can be
|
||||||
|
transformed using Scala’s full supplement of functional programming tools. In
|
||||||
|
order to retain type inference, there is a helper function which may be used in
|
||||||
|
case some common handling logic shall be applied to different clauses:
|
||||||
|
|
||||||
|
.. includecode:: code/akka/docs/actor/FSMDocSpec.scala
|
||||||
|
:include: transform-syntax
|
||||||
|
|
||||||
|
It goes without saying that the arguments to this method may also be stored, to
|
||||||
|
be used several times, e.g. when applying the same transformation to several
|
||||||
|
``when()`` blocks:
|
||||||
|
|
||||||
|
.. includecode:: code/akka/docs/actor/FSMDocSpec.scala
|
||||||
|
:include: alt-transform-syntax
|
||||||
|
|
||||||
Timers
|
Timers
|
||||||
------
|
------
|
||||||
|
|
||||||
Besides state timeouts, FSM manages timers identified by :class:`String` names.
|
Besides state timeouts, FSM manages timers identified by :class:`String` names.
|
||||||
You may set a timer using
|
You may set a timer using
|
||||||
|
|
||||||
:func:`setTimer(name, msg, interval, repeat)`
|
:func:`setTimer(name, msg, interval, repeat)`
|
||||||
|
|
||||||
where :obj:`msg` is the message object which will be sent after the duration
|
where :obj:`msg` is the message object which will be sent after the duration
|
||||||
:obj:`interval` has elapsed. If :obj:`repeat` is :obj:`true`, then the timer is
|
:obj:`interval` has elapsed. If :obj:`repeat` is :obj:`true`, then the timer is
|
||||||
|
|
@ -376,7 +358,7 @@ Termination from Inside
|
||||||
|
|
||||||
The FSM is stopped by specifying the result state as
|
The FSM is stopped by specifying the result state as
|
||||||
|
|
||||||
:func:`stop([reason[, data]])`
|
:func:`stop([reason[, data]])`
|
||||||
|
|
||||||
The reason must be one of :obj:`Normal` (which is the default), :obj:`Shutdown`
|
The reason must be one of :obj:`Normal` (which is the default), :obj:`Shutdown`
|
||||||
or :obj:`Failure(reason)`, and the second argument may be given to change the
|
or :obj:`Failure(reason)`, and the second argument may be given to change the
|
||||||
|
|
@ -389,25 +371,15 @@ state data which is available during termination handling.
|
||||||
the same way as a state transition (but note that the ``return`` statement
|
the same way as a state transition (but note that the ``return`` statement
|
||||||
may not be used within a :meth:`when` block).
|
may not be used within a :meth:`when` block).
|
||||||
|
|
||||||
.. code-block:: scala
|
.. includecode:: code/akka/docs/actor/FSMDocSpec.scala
|
||||||
|
:include: stop-syntax
|
||||||
when(A) {
|
|
||||||
case Event(Stop, _) =>
|
|
||||||
doCleanup()
|
|
||||||
stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
You can use :func:`onTermination(handler)` to specify custom code that is
|
You can use :func:`onTermination(handler)` to specify custom code that is
|
||||||
executed when the FSM is stopped. The handler is a partial function which takes
|
executed when the FSM is stopped. The handler is a partial function which takes
|
||||||
a :class:`StopEvent(reason, stateName, stateData)` as argument:
|
a :class:`StopEvent(reason, stateName, stateData)` as argument:
|
||||||
|
|
||||||
.. code-block:: scala
|
.. includecode:: code/akka/docs/actor/FSMDocSpec.scala
|
||||||
|
:include: termination-syntax
|
||||||
onTermination {
|
|
||||||
case StopEvent(Normal, s, d) => ...
|
|
||||||
case StopEvent(Shutdown, _, _) => ...
|
|
||||||
case StopEvent(Failure(cause), s, d) => ...
|
|
||||||
}
|
|
||||||
|
|
||||||
As for the :func:`whenUnhandled` case, this handler is not stacked, so each
|
As for the :func:`whenUnhandled` case, this handler is not stacked, so each
|
||||||
invocation of :func:`onTermination` replaces the previously installed handler.
|
invocation of :func:`onTermination` replaces the previously installed handler.
|
||||||
|
|
@ -419,7 +391,7 @@ When an :class:`ActorRef` associated to a FSM is stopped using the
|
||||||
:meth:`stop()` method, its :meth:`postStop` hook will be executed. The default
|
:meth:`stop()` method, its :meth:`postStop` hook will be executed. The default
|
||||||
implementation by the :class:`FSM` trait is to execute the
|
implementation by the :class:`FSM` trait is to execute the
|
||||||
:meth:`onTermination` handler if that is prepared to handle a
|
:meth:`onTermination` handler if that is prepared to handle a
|
||||||
:obj:`StopEvent(Shutdown, ...)`.
|
:obj:`StopEvent(Shutdown, ...)`.
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
|
|
@ -438,11 +410,11 @@ Event Tracing
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
The setting ``akka.actor.debug.fsm`` in :ref:`configuration` enables logging of an
|
The setting ``akka.actor.debug.fsm`` in :ref:`configuration` enables logging of an
|
||||||
event trace by :class:`LoggingFSM` instances::
|
event trace by :class:`LoggingFSM` instances:
|
||||||
|
|
||||||
class MyFSM extends Actor with LoggingFSM[X, Z] {
|
.. includecode:: code/akka/docs/actor/FSMDocSpec.scala
|
||||||
...
|
:include: logging-fsm
|
||||||
}
|
:exclude: body-elided
|
||||||
|
|
||||||
This FSM will log at DEBUG level:
|
This FSM will log at DEBUG level:
|
||||||
|
|
||||||
|
|
@ -459,17 +431,10 @@ Rolling Event Log
|
||||||
|
|
||||||
The :class:`LoggingFSM` trait adds one more feature to the FSM: a rolling event
|
The :class:`LoggingFSM` trait adds one more feature to the FSM: a rolling event
|
||||||
log which may be used during debugging (for tracing how the FSM entered a
|
log which may be used during debugging (for tracing how the FSM entered a
|
||||||
certain failure state) or for other creative uses::
|
certain failure state) or for other creative uses:
|
||||||
|
|
||||||
class MyFSM extends Actor with LoggingFSM[X, Z] {
|
.. includecode:: code/akka/docs/actor/FSMDocSpec.scala
|
||||||
override def logDepth = 12
|
:include: logging-fsm
|
||||||
onTermination {
|
|
||||||
case StopEvent(Failure(_), state, data) =>
|
|
||||||
log.warning(this, "Failure in state "+state+" with data "+data+"\n"+
|
|
||||||
"Events leading up to this point:\n\t"+getLog.mkString("\n\t"))
|
|
||||||
}
|
|
||||||
...
|
|
||||||
}
|
|
||||||
|
|
||||||
The :meth:`logDepth` defaults to zero, which turns off the event log.
|
The :meth:`logDepth` defaults to zero, which turns off the event log.
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue