Behaviors as finite state machines
You are viewing the documentation for the new actor APIs, to view the Akka Classic documentation, see Classic FSM.
An actor can be used to model a Finite State Machine (FSM).
To demonstrate this, consider an actor which shall receive and queue messages while they arrive in a burst and send them on after the burst ended or a flush request is received.
This example demonstrates how to:
- Model states using different behaviors
- Model storing data at each state by representing the behavior as a method
- Implement state timeouts
The events the FSM can receive become the type of message the Actor can receive:
- Scala
-
source
object Buncher { // FSM event becomes the type of the message Actor supports sealed trait Event final case class SetTarget(ref: ActorRef[Batch]) extends Event final case class Queue(obj: Any) extends Event case object Flush extends Event private case object Timeout extends Event }
- Java
SetTarget
is needed for starting it up, setting the destination for the Batches
to be passed on; Queue
will add to the internal queue while Flush
will mark the end of a burst.
- Scala
-
source
sealed trait Data case object Uninitialized extends Data final case class Todo(target: ActorRef[Batch], queue: immutable.Seq[Any]) extends Data final case class Batch(obj: immutable.Seq[Any])
- Java
Each state becomes a distinct behavior and after processing a message the next state in the form of a Behavior
is returned.
- Scala
-
source
object Buncher { // states of the FSM represented as behaviors // initial state def apply(): Behavior[Event] = idle(Uninitialized) private def idle(data: Data): Behavior[Event] = Behaviors.receiveMessage[Event] { message => (message, data) match { case (SetTarget(ref), Uninitialized) => idle(Todo(ref, Vector.empty)) case (Queue(obj), t @ Todo(_, v)) => active(t.copy(queue = v :+ obj)) case _ => Behaviors.unhandled } } private def active(data: Todo): Behavior[Event] = Behaviors.withTimers[Event] { timers => // instead of FSM state timeout timers.startSingleTimer(Timeout, 1.second) Behaviors.receiveMessagePartial { case Flush | Timeout => data.target ! Batch(data.queue) idle(data.copy(queue = Vector.empty)) case Queue(obj) => active(data.copy(queue = data.queue :+ obj)) } } }
- Java
The method idle
above makes use of Behaviors.unhandled
which advises the system to reuse the previous behavior, including the hint that the message has not been handled. There are two related behaviors:
- return
Behaviors.empty
as next behavior in case you reached a state where you don’t expect messages any more. For instance if an actor only waits until all spawned child actors stopped. Unhandled messages are still logged with this behavior. - return
Behaviors.ignore
as next behavior in case you don’t care about unhandled messages. All messages sent to an actor with such a behavior are simply dropped and ignored (without logging)
To set state timeouts use Behaviors.withTimers
along with a startSingleTimer
.
Example project
FSM example project is an example project that can be downloaded, and with instructions of how to run.
This project contains a Dining Hakkers sample illustrating how to model a Finite State Machine (FSM) with actors.