Mermoz
Mermoz

Reputation: 15474

Ask an actor and let him respond when he reaches a particular state in Akka 2

I'm quite new to Akka so my question may seem simple:

I have an actor called workerA that uses FSM and can thus be either in those two states Finishedand Computing:

sealed trait State
case object Finished extends State
case object Computing extends State

sealed trait Data
case object Uninitialized extends Data
case class Todo(target: ActorRef, queue: immutable.Seq[Any]) extends Data

When workerA receives GetResponse it should answer if and if only it is in state Finished.

What is the proper way of doing this? I know we should avoid to be blocking in this paradigm but here it is only the top actor which is concerned. Thanks

Upvotes: 1

Views: 317

Answers (2)

yǝsʞǝla
yǝsʞǝla

Reputation: 16412

You can pattern match on FSM states:

// insert pattern matching stuff instead of ...

class MyActor extends Actor with FSM[State, Message] {
  startWith(Finished, WaitMessage(null))

  when(Finished) {
    case Event(Todo(... =>
      // work
      goto(Computing) using Todo(...)
    case Event(GetResponse(... =>
      // reply: sender ! msg // or similar
  }

  /* the rest is optional. You can use onTransition below to send yourself a message to    report status of the job: */
  when(Busy) {
    case Event(Finished(... =>
      // reply to someone: sender ! msg // or similar
      goto(Finished)
  }

  onTransition {
    case Finished -> Computing =>
      // I prefer to run stuff here in a future, and then send a message to myself to signal the end of the job:
      self ! Finished(data)
  }

An Edit to more specifically address the question:

class MyActor extends Actor with FSM[State, Message] {
  startWith(Finished, WaitMessage(null))

  when(Finished) {
    case Event(Todo(... =>
      // work
      goto(Computing) using Todo(...)
    case Event(GetResponse(... =>
      // reply: sender ! msg // or similar
      stay
  }

  initialize()
}

Upvotes: 0

cmbaxter
cmbaxter

Reputation: 35443

I'm not necessarily sure you even need FSM here. FSM is a really good tool for when you have many states and many possible (and possibly complicated) state transitions between those states. In your case, if I understand correctly, you basically have two states; gathering data and finished. It also seems that there is only a single state transition, going from gathering -> finished. If I have this all correct, then I'm going to suggest that you simply use become to solve your problem.

I have some code below to show a trivial example of what I'm describing. The basic idea is that the main actor farms some work off to some workers and then waits for the results. If anyone asks for the results while the work is being done, the actor stashes that request until the work is done. When done, the actor will reply back to anyone that has asked for the results. The code is as follows:

case object GetResults
case class Results(ints:List[Int])
case object DoWork

class MainActor extends Actor with Stash{
  import context._

  override def preStart = {
    val a = actorOf(Props[WorkerA], "worker-a")
    val b = actorOf(Props[WorkerB], "worker-b")
    a ! DoWork
    b ! DoWork
  }

  def receive = gathering(Nil, 2)

  def gathering(ints:List[Int], count:Int):Receive = {
    case GetResults => stash()
    case Results(i) =>      
      val results = i ::: ints
      val newCount = count - 1
      if (newCount == 0){
        unstashAll()
        become(finished(results))        
        child("worker-a") foreach (stop(_))
        child("worker-b") foreach (stop(_))
      }
      else
        become(gathering(results, newCount))
  } 

  def finished(results:List[Int]):Receive = {
    case GetResults => sender ! results
  }
}

class WorkerA extends Actor{
  def receive = {
    case DoWork =>

      //Only sleeping to simulate work.  Not a good idea in real code
      Thread sleep 3000
      val ints = for(i <- 2 until 100 by 2) yield i
      sender ! Results(ints.toList)
  }
}

class WorkerB extends Actor{
  def receive = {
    case DoWork =>

      //Only sleeping to simulate work.  Not a good idea in real code
      Thread sleep 2000  
      val ints = for(i <- 1 until 100 by 2) yield i
      sender ! Results(ints.toList)      
  }
}

Then you could test it as follows:

val mainActor = system.actorOf(Props[MainActor])
val fut = mainActor ? GetResults
fut onComplete (println(_))

Upvotes: 1

Related Questions