Single-Page

The Dining Philosophers in Scala Akka FSM

Prabhaker Mateti

pmateti@wright.edu

Assuming familiarity with the Actors, Scala and Akka
CEG 7370 Distributed Computing

Table of Contents

1 The FSM (Finite State Machine)

  1. FSMs are typically visualized as graphs, with nodes representing states, and directed edges representing transitions.
  2. Example drawing coming up …
  3. If FSM is in state S and the event E occurs, it takes the actions A and transitions into the state S'. FSM: State X Event –> Actions X State
  4. FSMs are strongly related to regex.

1.1 An example: Vending Machine FSM.

  1. [from Kenneth Rosen, Discrete Math and its Applications, 7e, Section 13.2]
fig-actor-users.png

2 Dining Philosophers using Akka FSM

  1. Full source code DiningHakkersOnFsm.scala, includes some mods.
  2. Explained below piecemeal.
  3. Assumes familiarity with the Dining Philosophers scenario. Read Andrews book section on "Dining Philosophers Revisited."
  4. Akka FSM is an OOP mixin
  5. We skip all OOP details of Scala in this lecture. Such as: sealed trait, case object, case class, …

2.1 object DiningHakkersOnFsm

object DiningHakkersOnFsm {
  val system = ActorSystem()
  def main(args: Array[String]) {
    val chopsticks =
      for (i <- 0 to 4) yield system.actorOf(
         Props[Chopstick], "C" + i)
    val hakkers = for {
      (name, i) <- List("P0", "P1", "P2", "P3", "P4").
           zipWithIndex
    } yield system.actorOf(
        Props(classOf[FSMHakker], 
              name, chopsticks(i),
              chopsticks((i + 1) % 5)))
    hakkers.foreach(_ ! Think)
  }
}
  1. chopsticks array of actors, indexed from 0 to 4, named C0, …, C4.
  2. zipWithIndex constructs ("P0", 0), …, ("P4", 4).
  3. All actors in hakkers are sent the message Think

2.2 ChopstickState

  1. sealed trait ChopstickState
    case object Available extends ChopstickState
    case object Taken extends ChopstickState
    
    case class TakenBy(hakker: ActorRef)
    
  2. There are two ChopstickState s for the chopsticks: Available, Taken
  3. TakenBy is a class with one constructor that requires a hakker as its argument. The ActorRef better be a "hakker" actor.

2.3 Chopstick Class

class Chopstick extends Actor 
    with FSM[ChopstickState, TakenBy] {
  import context._
  startWith(Available, TakenBy(system.deadLetters))
  when(Available) ...
  when(Taken) ...
  initialize
}
  1. startWith defines the initial state and initial data. system.deadLetters cf. "nobody"
  2. when(state) ... per state.
  3. initialize: now the actor is placed in the initial state defined in startWith. Until now, this actor was in "limbo".
  4. All these (startWith, when, initialize) are from Akka FSM.

2.4 ChopstickMessage

sealed trait ChopstickMessage
object Take extends ChopstickMessage
object Put extends ChopstickMessage
case class Taken(chopstick: ActorRef)
        extends ChopstickMessage
case class Busy(chopstick: ActorRef)
        extends ChopstickMessage

2.5 Chopstick State: Available

when(Available) {
  case Event(Take, _) =>
    goto(Taken) 
       using TakenBy(sender) 
          replying Taken(self)
}
  1. The chopstick actor does the following when it is in state Available.
  2. If the event Take happens, whatever the second parameter may be, transition into state Taken.
  3. TakenBy the sender actor of this event.
  4. Invoke the method Taken with itself as the chopstick.
  5. using and replying are modifier words.

2.6 A Scala Aside: goto

  1. This "goto" is related to the famous "goto considered harmful" of Dijkstra. But, Scala's goto is nicely refined.
  2. Scala is using delimited continuations, of the functional programming world, in defining this goto.
  3. Ex: A method from the Scala Swarm library, "… stops the execution of your code at one point, and the remaining computation becomes the continuation. … transfers the computation to another host," and returns the result to the stopped computation.

2.7 Chopstick State: Taken

when(Taken) {
  case Event(Take, currentState) =>
    stay replying Busy(self)
  case Event(Put, TakenBy(hakker)) 
    if sender == hakker =>
       goto(Available) using TakenBy(system.deadLetters)
}
  1. If a Take event happens, the state stay s the same.
  2. Invoke the method Busy with itself as the chopstick.
  3. If a Put happens, it better be by whoever took this chopstick. If so, transition into Available. Who has it now? Nobody (i.e., system.deadLetters)

3 FSMHakkerState

sealed trait FSMHakkerMessage
object Think extends FSMHakkerMessage

sealed trait FSMHakkerState
case object Thinking extends FSMHakkerState
case object Eating extends FSMHakkerState
case object Hungry extends FSMHakkerState
case object Waiting extends FSMHakkerState
case object WaitForOtherChopstick extends FSMHakkerState
case object FirstChopstickDenied extends FSMHakkerState

case class TakenChopsticks(
  left: Option[ActorRef], right: Option[ActorRef])
  1. The states are all disjoint. An actor cannot be in two or more states simultaneously.
  2. Advanced Scala: the Option[T] type. It is a container for an optional value of type T. Cf. None, null, nil, …

4 class FSMHakker

class FSMHakker (
  name: String, left: ActorRef, right: ActorRef)
  extends Actor with 
  FSM[FSMHakkerState, TakenChopsticks] {
  startWith(Waiting, TakenChopsticks(None, None))
  when(Waiting) ...
  when(Thinking) ...
  when(Hungry) ...
  when(WaitForOtherChopstick) ...
  when(FirstChopstickDenied) ...
  when(Eating) ...
  initialize
}
  1. Philosopher has name, left, right fields.
  2. Philosopher starts in Waiting state, with no chopsticks.

4.1 Waiting State

when(Waiting) {
  case Event(Think, _) =>
    println( s"$name starts to think" )
    startThinking(5.seconds)
}

private def startThinking(duration: FiniteDuration) = {
  goto(Thinking) using 
    TakenChopsticks(None, None) forMax duration
}
  1. Soon receives Think message from main object DiningHakkersOnFsm.

4.2 Thinking State

when(Thinking) {
  case Event(StateTimeout, _) =>
    left ! Take
    right ! Take
    goto(Hungry)
}
  1. In Thinking state, philosopher does not have any chopsticks. This state ends (i.e., a transition will happen) in a finite amount of time.
  2. At the end of the timeout, sends the Take message to chopstcks left and right.
  3. Enters Hungry state.

4.3 Eating State

when(Eating) {
  case Event(StateTimeout, _) =>
    println(s"$name puts down and starts to think")
    left  ! Put
    right ! Put
    startThinking(5.seconds)
}
  1. In Eating state, philosopher does have two chopsticks. This state ends in a finite amount of time.

4.4 Hungry State

when(Hungry) {
  case Event(Taken(`left`), _) =>
    goto(WaitForOtherChopstick) using 
      TakenChopsticks(Some(left), None)
  case Event(Taken(`right`), _) =>
    goto(WaitForOtherChopstick) using 
      TakenChopsticks(None, Some(right))
  case Event(Busy(_), _) =>
    goto(FirstChopstickDenied)
}
  1. Note the back ticks.
  2. Advanced Scala: Some(x) Some[A] represents existing values of type A
  3. No chopsticks: enter FirstChopstickDenied state.

4.5 WaitForOtherChopstick

when(WaitForOtherChopstick) {
  case Event(Taken(`left`), 
    TakenChopsticks(None, Some(right))) =>
        startEating(left, right)
  case Event(Taken(`right`), 
    TakenChopsticks(Some(left), None)) =>
        startEating(left, right)
  case Event(Busy(chopstick), 
    TakenChopsticks(leftOption, rightOption)) =>
        leftOption.foreach(_ ! Put)
        rightOption.foreach(_ ! Put)
        startThinking(10.milliseconds)
}
  1. On entering this state: Already have one chopstick. Need the other.

4.6 startEating

private def startEating(
  left: ActorRef, right: ActorRef) = { println(
    s"$name has $left and $right and starts to eat")
  goto(Eating) using TakenChopsticks(
    Some(left), Some(right)) forMax (5.seconds)
}

4.7 FirstChopstickDenied

when(FirstChopstickDenied) {
  case Event(Taken(secondChopstick), _) =>
    secondChopstick ! Put
    startThinking(10.milliseconds)
  case Event(Busy(chopstick), _) =>
    startThinking(10.milliseconds)
}
  1. "First" refers to the chopstick in the left hand. Second = right hand.

5 Exercises

  1. Draw a state diagram for a philosopher as constructed here.
  2. Should this program exhibit livelock? deadlock? individual starvation?
  3. How do you orchestrate (cause) these?
  4. Introduce a "waiter" actor to solve the above problems.
  5. Would prioritizing the chopsticks and/or philosophers enable a solution without needing a waiter?
  6. Allow philosophers to request the fork(s) from each other. Is this permissible?
  7. When to use Akka FSM? When to stay with become?

6 References

  1. Akka doc http://doc.akka.io/docs/akka/snapshot/scala/fsm.html
  2. Rick Molloy, "Solving The Dining Philosophers Problem With Asynchronous Agents", Visual C++. 2010. http://msdn.microsoft.com/en-us/magazine/dd882512.aspx Suggested Reading.
  3. http://rosettacode.org/wiki/Dining_philosophers implemented in 35+ (famous and not-so-famous) programming languages using various concurrency primitives. Highly Recommended Reading.