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) } }
chopsticks array of actors, indexed from 0 to 4, named C0, …,
C4.
zipWithIndex constructs ("P0", 0), …, ("P4", 4).
hakkers are sent the message Think
sealed trait ChopstickState case object Available extends ChopstickState case object Taken extends ChopstickState case class TakenBy(hakker: ActorRef)
ChopstickState s for the chopsticks: Available,
Taken
TakenBy is a class with one constructor that requires a hakker
as its argument. The ActorRef better be a "hakker" actor.
class Chopstick extends Actor with FSM[ChopstickState, TakenBy] { import context._ startWith(Available, TakenBy(system.deadLetters)) when(Available) ... when(Taken) ... initialize }
startWith defines the initial state and initial data.
system.deadLetters cf. "nobody"
when(state) ... per state.
initialize: now the actor is placed in the initial
state defined in startWith. Until now, this actor was in
"limbo".
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
when(Available) {
case Event(Take, _) =>
goto(Taken)
using TakenBy(sender)
replying Taken(self)
}
Available.
Take happens, whatever the second parameter may be,
transition into state Taken.
TakenBy the sender actor of this event.
Taken with itself as the chopstick.
using and replying are modifier words.
when(Taken) {
case Event(Take, currentState) =>
stay replying Busy(self)
case Event(Put, TakenBy(hakker))
if sender == hakker =>
goto(Available) using TakenBy(system.deadLetters)
}
Take event happens, the state stay s the same.
Busy with itself as the chopstick.
Put happens, it better be by whoever took this chopstick.
If so, transition into Available. Who has it now? Nobody (i.e.,
system.deadLetters)
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])
Option[T] type. It is a container for an
optional value of type T. Cf. None, null, nil, …
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 }
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
}
DiningHakkersOnFsm.
when(Thinking) {
case Event(StateTimeout, _) =>
left ! Take
right ! Take
goto(Hungry)
}
Thinking state, philosopher does not have any chopsticks.
This state ends (i.e., a transition will happen) in a finite amount
of time.
left
and right.
Hungry state.
when(Eating) {
case Event(StateTimeout, _) =>
println(s"$name puts down and starts to think")
left ! Put
right ! Put
startThinking(5.seconds)
}
Eating state, philosopher does have two chopsticks. This state
ends in a finite amount of time.
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)
}
Some(x) Some[A] represents existing values of type A
FirstChopstickDenied state.
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)
}
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) }
when(FirstChopstickDenied) {
case Event(Taken(secondChopstick), _) =>
secondChopstick ! Put
startThinking(10.milliseconds)
case Event(Busy(chopstick), _) =>
startThinking(10.milliseconds)
}
become?