Saturday, November 15, 2014

State Machine with Akka Actors using Become/UnBecome

I came across very interesting use of Scala and Akka's Become/Unbecome feature to write State Transition Machine based on Actor Model.

As we know, Akka is about Actors and their communication. Communication between actor can happen by sending messages. They dont call methods on each other directly. Every Actor possesses a personal mailbox. It goes and picks new messages from mailbox. Reacts to them. And then send mails to other Actor as part of it's reaction. Pretty Neat !



This is how sample Actor looks like listening to messages and reacting to them:


class SampleActor extends Actor with ActorLogging{

  def receive = {

    case "Hello" => log.info("Hello")

    case _      => log.info("Unknown")

  }

}

"receive" is method in Actor class which gets called when it receives a message. So whenever SampleActor gets a message of type Hello, it will react to it by "log.info("Hello")".

Now what if we can change behaviour of an Actor ? Changing behaviour means which type of messages it will accept and how it will react to them. Thats where we use Become/UnBecome feature of Actors.

class SampleActor extends Actor with ActorLogging {

  import context._

  def receive = {
    case "Hello" =>
    log.info("Hello")
    become(bye)
    case _      => log.info("Unknown")
  }

  def bye: Receive = {
  case "Bye" =>
  log.info("Bye")
  //unbecome() // if unbecome now then it will fall back to default message handling behaviour as implemented in receive method
  case _      => log.info("Unknown")
  }
}

As shown above with method "def bye: Receive" we have added new type of behaviour into SampleActor to respond to "Bye" events. This method is message handler method of type PartialFunction[Any, Unit]. Once it receives "Bye" it will respond to it by "log.info("Bye")". After handling "Bye" message an actor can decide to go back to original state by calling unbecome() on the context. So in other words, Actor's message handling behaviours are stacked. Newly Become behaviour gets added on top of the stack. Once it calls Unbecome behaviour on top of the stack it gets popped out and we are back to old behaviour.

It sounds more like changing strategies. However unlike strategy pattern we can change a behaviour of an Actor so that it will listen to completely different types of messages. Not just responding differently to same type of messages as in the case of strategy pattern.
If we apply this concept to some problem context then we can think of this as Actor changing its state. In Every State he can listen to messages applicable to that state. Based on certain criteria we decide to change State and then it will receive/accept messages applicable to that State.

A good example, to be really able to appreciate this feature, would be Dining Philopher Problem. Just to brief on Dining Philosopher problem, We have a dining table with 5 philosopher. Philosophers thinks a lot. After thinking for a some time he gets hungry. To eat he first picks one chopstick on left. Then one on his right. Once he has both, he can start eating. Done with the eating, he drops chopsticks one at a time starting with right stick first. Then goes back to thinking. This example as mentioned here  and code here is very good to understand the power of changing behavior using become/unbecome.
Dining philosopher is very old school example to help us understand problems around resource sharing in concurrent applications. With Akka, you can think about this from Event based or Reactive perspective. Diagram below shows state transitions for philosopher and Chopstick. It shows how each entity transit from one state to another based on events that it receives.


When modeled above problem using Actor model this how it will look. Both actors receive few events either from other actor or self generated one.

So we have 2 types Actors here. Philosopher and ChopStick. Each can receive certain event and then respond to them by sending out certain new events. On receiving events Actor can move to new state as shown in State Transition Diagram above. These events and states have certain logical ordering. Philopher can never process "Eating" Event while in "Waiting for Left Chostick" State, because only when he grabs both chopsticks he can start eating. That's where State Pattern comes handy. It allows you to identify and toggle behaviour through different states by applying some rules. Without any complicated if-this-then-that kind of code.

I am just taking small portion from that code to explain how Become/UnBecome can be used in Actors to achieve state transition as shown above. Just to note, Philosophers are mentioned as Hakker there.

class Philosopher(name: String, left: ActorRef, right: ActorRef) extends Actor {

import context._

//All hakkers start in a non-eating state
  def receive = {
    case Think =>
      println("%s starts to think".format(name))
      startThinking(5.seconds)
  }

  private def startThinking(duration: FiniteDuration): Unit = {
    become(thinking)
    system.scheduler.scheduleOnce(duration, self, Eat)
  }


  def thinking: Receive = {
    case Eat =>
      become(hungry)
      left ! Take(self)
      right ! Take(self)
  }

  .
  .
  .
}

Philosopher takes reference to left and righ chopstick. Default behaviour is to listen to Think event. Once he starts thinking he goes to "thinking" State. Now he can listen to "Eat" events. As you can see, the way it responds to Eat event is to first try grabbing left chopstick. Now here Philosopher actor sends "Take" message/request to left chopstick actor.


class Chopstick extends Actor {

  import context._

  //When a Chopstick is taken by a hakker
  //It will refuse to be taken by other hakkers
  //But the owning hakker can put it back
  def takenBy(hakker: ActorRef): Receive = {
    case Take(otherHakker) =>
      otherHakker ! Busy(self)
    case Put(`hakker`) =>
      become(available)
  }

  //When a Chopstick is available, it can be taken by a hakker
  def available: Receive = {
    case Take(hakker) =>
      become(takenBy(hakker))
      hakker ! Taken(self)
  }

  //A Chopstick begins its existence as available
  def receive = available
}

Chopstick actors are by default in "available" state which means that it can listen to "Take" kind of events. Once it receivs "Take" event in "available" state it simply gives Philosopher an access and sends "Taken" ack message back to the original Philosopher actor.

This is very classical problem with quite obvious state based behaviour. Event based communution in Actor model and Become/Unbecome feature really makes it very easy to implement. All state changes happens with simple Become and UnBecome switches. All we have to do is to write message handlers (PartialFunction[Any, Unit]) and keep switching between them. Just Imagine writing same state pattern implementation using only Java. There will be definitely more number of lines and not sure if that will be as readable as this one here. And above all we are using Akka. Instead of calling methods directly we are sending/reacting to events. Which means It can only get more scalable from here.

No comments: