Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Initializing an actor before being able to handle some other messages

I have an actor which creates another one:

class MyActor1 extends Actor {
  val a2 = system actorOf Props(new MyActor(123))
}

The second actor must initialize (bootstrap) itself once it created and only after that it must be able to do other job.

class MyActor2(a: Int) extends Actor {
  //initialized (bootstrapped) itself, potentially a long operation 
  //how?
  val initValue = // get from a server

  //handle incoming messages
  def receive = {
    case "job1" => // do some job but after it's initialized (bootstrapped) itself
  }
}

So the very first thing MyActor2 must do is do some job of initializing itself. It might take some time because it's request to a server. Only after it finishes successfully, it must become able to handle incoming messages through receive. Before that - it must not do that.

Of course, a request to a server must be asynchronous (preferably, using Future, not async, await or other high level stuff like AsyncHttpClient). I know how to use Future, it's not a problem, though.

How do I ensure that?

p.s. My guess is that it must send a message to itself first.

like image 277
Incerteza Avatar asked Dec 15 '13 04:12

Incerteza


People also ask

Which method provides initial Behaviour to actor?

Start Hook This method is called when the actor is first created.

What is actor model in Akka?

Akka Actors The Actor Model provides a higher level of abstraction for writing concurrent and distributed systems. It alleviates the developer from having to deal with explicit locking and thread management, making it easier to write correct concurrent and parallel systems.


2 Answers

You could use become method to change actor's behavior after initialization:

class MyActor2(a: Int) extends Actor {

  server ! GetInitializationData

  def initialize(d: InitializationData) = ???

  //handle incoming messages
  val initialized: Receive = {
    case "job1" => // do some job but after it's initialized (bootstrapped) itself
  }

  def receive = {
    case d @ InitializationData =>
      initialize(d)
      context become initialized
  }
}

Note that such actor will drop all messages before initialization. You'll have to preserve these messages manually, for instance using Stash:

class MyActor2(a: Int) extends Actor with Stash {

  ...

  def receive = {
    case d @ InitializationData =>
      initialize(d)
      unstashAll()
      context become initialized
    case _ => stash()
  }
}

If you don't want to use var for initialization you could create initialized behavior using InitializationData like this:

class MyActor2(a: Int) extends Actor {

  server ! GetInitializationData

  //handle incoming messages
  def initialized(intValue: Int, strValue: String): Receive = {
    case "job1" => // use `intValue` and `strValue` here
  }

  def receive = {
    case InitializationData(intValue, strValue) =>
      context become initialized(intValue, strValue)
  }
}
like image 65
senia Avatar answered Nov 13 '22 02:11

senia


I don't know wether the proposed solution is a good idea. It seems awkward to me to send a Initialization message. Actors have a lifecycle and offer some hooks. When you have a look at the API, you will discover the prestart hook.

Therefore i propose the following:

  • When the actor is created, its preStart hook is run, where you do your server request which returns a future.
  • While the future is not completed all incoming messages are stashed.
  • When the future completes it uses context.become to use your real/normal receive method.
  • After the become you unstash everything.

Here is a rough sketch of the code (bad solution, see real solution below):

class MyActor2(a: Int) extends Actor with Stash{

  def preStart = {
    val future = // do your necessary server request (should return a future)
    future onSuccess {
      context.become(normalReceive)
      unstash()
    }
  }

  def receive = initialReceive

  def initialReceive = {
    case _ => stash()
  }

  def normalReceive = {
    // your normal Receive Logic
  }
}

UPDATE: Improved solution according to Senias feedback

class MyActor2(a: Int) extends Actor with Stash{

  def preStart = {
    val future = // do your necessary server request (should return a future)
    future onSuccess {
      self ! InitializationDone
    }
  }

  def receive = initialReceive

  def initialReceive = {
    case InitializationDone =>
      context.become(normalReceive)
      unstash()
    case _ => stash()
  }

  def normalReceive = {
    // your normal Receive Logic
  }

  case class InitializationDone
}
like image 31
mavilein Avatar answered Nov 13 '22 02:11

mavilein