Akka Improve Scala28 juni 2014

Akka – Ask pattern

In Akka you would often like to use some form of request-response. In this blog I will show some how this can be done and what you should think about.

Plain tell – tell

The easiest way to implement request response is just using tell

class Client(timeTeller: ActorRef) extends Actor with ActorLogging {
  // do not initialize a variable like this. Only demo purposes
  var lastKnownTime: Date = _ 
  timeTeller ! GetTime
  def receive = {
    case date: Date => 
      lastKnownTime = date
      log.info("The time: {}", lastKnownTime)
  }
}

class TimeTeller extends Actor {
  def receive = {
    case GetTime => sender() ! new Date()
  }
}

In the above code the TimeTeller will respond to the sender of the GetTime message (in this case the Client) with the current time.

Ask pattern

In many cases the previous solution will not be sufficient. Akka has location transparency and no guaranteed delivery (which is another discussion by itself). Loosing the GetTime message will result in a client that waits infinitely. This is somethig you would like to prevent. In other cases you want to set a timeout so that processing can continue, or you can respond to the user in a timely. These are all situation which can be implemented by using the ask pattern with a timeout:

import akka.pattern.ask
import context.dispatcher

implicit val timeout: Timeout = 2.seconds
val time: Future[Any] = timeTeller ? GetTime

To make the ask pattern work you need three things:

  1. import the pattern
  2. a dispatcher to execute the future
  3. an implicit timeout

The ? will then return Future that will be completed with success (and holds the Date object) when the response comes in time. When a timeout occurs the Future will be completed with an exception, namely akka.pattern.AskTimeoutException.

When you know Futures you will be tempted to make a composition with the callback functions. You should not do this, unless you are absolutely sure what you are doing. It is very easy to turn actor state into shared mutable state by the actor variables in a callback. The callback is exuted by a different thread and it might well be the case that the actor is handling a message at the same time the callback is executed.

The best way to handle the response is to send the result of the future as a message to the actor and than handing this result the same way as you did before, plus extra failure handling.

(timeTeller ? GetTime) pipeTo self

def receive = {
  case date: Date => // same as before
  case Failure(exception: AskTimeoutException) => 
    // handle the failure
    log.error("No time received.")
}

The main goal of the ask pattern in Akka is to bound the request with a time constraint. There are cases where you certainly want to compose Futures and use the callback functions. In any case be sure that you will not change the actor state or close over the actor state.

For more information see the Akka documentation