Akka FSM Actor with stashing and unstashing

878 views Asked by At

I would like to do stashing/unstashing with FSM Akka Acctor. I don't know where to put the stash() and unstashAll().

I have a simplified example below:

import akka.actor.{ActorSystem, FSM, Props, Stash}

trait TestState
case object StateA extends TestState
case object StateB extends TestState

case class TestData()

case class MessageA(msg: String)
case class MessageB(msg: String)
case object ChangeState

class TestFSM extends FSM[TestState, TestData] with Stash {

  startWith(StateA, TestData())

  when(StateA) {
    case Event(MessageA(msgA), _) =>
      println(s"In StateA: $msgA")
      stay()
    case Event(ChangeState, _) =>
      println("Changing state from A to B")
      goto(StateB)
  }

  when(StateB) {
    case Event(MessageB(msgB), _) =>
      println(s"In StateB: $msgB")
      stay()
  }

  whenUnhandled {
    case Event(e, _) =>
      println(s"Unhandled event: $e")
      stay()
  }
}

object TestFSM extends App {
  val system = ActorSystem("test-system")
  val actor = system.actorOf(Props[TestFSM])

  actor ! MessageA("Apple 1")
  actor ! MessageB("Banana 1")
  actor ! MessageA("Apple 2")

  actor ! ChangeState

  actor ! MessageB("Banana 2")
}

The initial state is StateA. When in StateA the actor should only handle messages of type MessageA. If it receives any other type of message (except for ChangeState) it should stash it. Upon receiving a message ChangeState, the actor should change to StateB. Upon changing from StateA to StateB, it should unstash all the messages. When in StateB the actor should only handle messages of type MessageB.

I am not sure exactly where to use the stash() and unstashAll() to achieve this.

The output that I get on running is:

In StateA: Apple 1
Unhandled event: MessageB(Banana 1)
In StateA: Apple 2
Changing state from A to B
In StateB: Banana 2

The output I would like to see is:

In StateA: Apple 1
In StateA: Apple 2
Changing state from A to B
In StateB: Banana 1
In StateB: Banana 2

Thanks a lot.

1

There are 1 answers

1
Andrei T. On BEST ANSWER

You can achieve this by using the onTransition method on the FSM. This gets executed when a state change occurs and we can use that moment to unstash all messages. As for stashing, you need to do it in the whenUnhandled method. I've also made a small update so you can cycle between states:

class TestFSM extends FSM[TestState, TestData] with Stash {
  startWith(StateA, TestData())

  when(StateA) {
    case Event(MessageA(msgA), _) =>
      println(s"In StateA: $msgA")
      stay()
    case Event(ChangeState, _) =>
      println("Changing state from A to B")
      goto(StateB)
  }

  when(StateB) {
    case Event(MessageB(msgB), _) =>
      println(s"In StateB: $msgB")
      stay()
    case Event(ChangeState, _) =>
      println("Changing state from B to A")
      goto(StateA)
  }

  /**
    * Here we can stash all messages. For example when we're in state A,
    * we handle both `MessageA` and `ChangeState` messages, but we don't
    * handle `MessageB` instances which will end up here. The opposite
    * happens when we're in state B.
    */
  whenUnhandled {
    case _: Event =>
      stash()
      stay()
  }

  // When transitioning into another state, unstash all messages.
  onTransition {
    case StateA -> StateB => unstashAll()
    case StateB -> StateA => unstashAll()
  }
}

Let me know if you have any more questions :)