scalaakkaakka-actor

Akka Classic Ask pattern. How does it match asks with responses?


I'm a newbie with Akka Actors, and I am learning about the Ask pattern. I am looking at the following example from alvin alexander:

class TestActor extends Actor {
  def receive = {
    case AskNameMessage => // respond to the "ask" request
                           sender ! "Fred"
    case _ => println("that was unexpected")
  }
}

...

  val myActor = system.actorOf(Props[TestActor], name = "myActor")

  // (1) this is one way to "ask" another actor
  implicit val timeout = Timeout(5 seconds)
  val future = myActor ? AskNameMessage
  val result = Await.result(future, timeout.duration).asInstanceOf[String]
  println(result)

(Yes, I know that Await.result isn't generally the best practice, but this is just a simple example.)

So from what I can tell, the only thing you need to do to implement the "askee" actor to service an Ask operation is to send a message back to the "asker" via the Tell operator, and that will be turned into a future on the "asker" side as a response to the Ask. Seems simple enough.

My question is this:

When the response comes back, how does Akka know that this particular message is the response to a certain Ask message?

In the example above, the "Fred" message doesn't contain any specific routing information that specifies that it's the response to a particular Ask operation. Does it just assume that the next message that the asker receives from that askee is the answer to the Ask? If that's the case, then what if one actor sends multiple Ask operations to the same askee? Wouldn't the responses likely get jumbled, causing random responses to be mapped to the wrong Asks?

Or, what if the asker is also receiving other types of messages from the same askee actor that are unrelated to these Ask messages? Couldn't the Asks receive response messages of the wrong type?

Just to be clear, I'm asking about Akka Classic, not Typed.


Solution

  • For every Ask message sent to an actor, akka creates a proxy ActorRef whose sole responsibility is to process one single message. This temp "actor" is initialized with a promise, which it needs to complete on message processing.

    The source code of it is found here

    but the main details are

    private[akka] final class PromiseActorRef private (
        val provider: ActorRefProvider,
        val result: Promise[Any],
    ....
    
        val alreadyCompleted = !result.tryComplete(promiseResult)
    

    Now, it should be clear that Ask pattern is backed by independent unique actor asker for every message sent to the receiver askee.

    The askee does know actor reference of the sender, or asker, of every message received via method context.sender(). Thus, it just needs to use this ActorRef to send a response back to the asker.

    Finally, this all avoids any race conditions given that an actor only processes a message at a time. Thus it excludes any possibility of retrieving a "wrong" asker via method context.sender().