MonitoringActor

NetworkActor

CheckHealth

Ping

NetworkActor

Pong

MonitoringActor

Up

MonitoringActor

Down

NetworkActor

Pong









Ordinary actor

MonitoringActor

Pong

class MonitoringActor extends Actor with ActorLogging { private val networkActor = context.actorOf(Props[NetworkActor], "network") private var origin: Option[ActorRef] = None def receive = { case CheckHealth => networkActor ! Ping origin = Some(sender) case Pong => origin.foreach(_ ! Up) origin = None } }

NetworkActor

Pong

Ping

MonitoringActor

CheckHealth

Pong

NetworkActor

origin

CheckHealth

Subsequent CheckHealth will overwrite previous origin

will overwrite previous CheckHealth should not really be allowed when waiting for Pong

should not really be allowed when waiting for If Pong never arrives we are left in inconsistent state

never arrives we are left in inconsistent state ...because we don't have 1 second timeout condition yet

class MonitoringActor extends Actor with ActorLogging { private val networkActor = context.actorOf(Props[NetworkActor], "network") def receive = waitingForCheckHealth private def waitingForCheckHealth: Receive = { case CheckHealth => networkActor ! Ping context become waitingForPong(sender) } private def waitingForPong(origin: ActorRef): Receive = { case Pong => origin ! Up context become waitingForCheckHealth } }

context.become()

CheckHealth

Pong

origin

waitingForPong()

origin

PartialFunction

Pong

def receive = waitingForCheckHealth private def waitingForCheckHealth: Receive = { case CheckHealth => networkActor ! Ping implicit val ec = context.dispatcher val timeout = context.system.scheduler. scheduleOnce(1.second, self, Down) context become waitingForPong(sender, timeout) } private def waitingForPong(origin: ActorRef, timeout: Cancellable): Receive = LoggingReceive { case Pong => timeout.cancel() origin ! Up context become receive case Down => origin ! Down context become receive }

Ping

Down

waitingForPong

Pong

Down

Up

Down

Down

MonitoringActor

CheckHealth

Up

Down

Composing futures

ask

def receive = { case CheckHealth => implicit val timeout: Timeout = 1.second implicit val ec = context.dispatcher val origin = sender networkActor ? Ping andThen { case Success(_) => origin ! Up case Failure(_) => origin ! Down } }

networkActor

Ping

Success(_)

_

Pong

Up

Failure(_)

_

AskTimeout

Down

sender

sender

Pong

sender

origin

pipeTo

def receive = LoggingReceive { case CheckHealth => implicit val ec = context.dispatcher networkActor.ask(Ping)(1.second). map{_ => Up}. recover{case _ => Down}. pipeTo(sender) }

ask

?

networkActor

Up

Down

sender

sender

pipeTo()

ActorRef

sender

Dedicated actor

NetworkActor

MonitoringActor

class MonitoringActor extends Actor with ActorLogging { def receive = { case CheckHealth => context.actorOf(Props(classOf[PingActor], networkActor, sender)) } }

PingActor

class PingActor(networkActor: ActorRef, origin: ActorRef) extends Actor with ActorLogging { networkActor ! Ping context.setReceiveTimeout(1.second) def receive = { case Pong => origin ! Up self ! PoisonPill case ReceiveTimeout => origin ! Down self ! PoisonPill } }

Ping

NetworkActor

Pong

Down

PingActor

MonitoringActor

NetworkActor

Imagine a simple Akka actor system consisting of two parties:and. Whenever someone () sendsto the former one it asks the latter by sendingis obligated to reply withas soon as possible (scenario [A]). Oncereceives such a reply it immediately replies to the client withstatus message. Howeveris obligated to sendreply iffailed to respond withwithin one second (scenario [B]). Both workflows are depicted below:Apparently there are at least three ways to implement this simple task in Akka and we shall study their pros and cons.In this scenariolistens fordirectly without any intermediaries:The implementation ofis irrelevant, just assume it responds withfor each. As you can seehandles two messages:sent by the client andsent presumably by the. Sadly we had to store the client reference underfield because it would have been lost otherwise oncewas handled. So we added a bit of state. The implementation is quite straightforward but has quite a few issues:But before we implement timeout condition let's refactor our code a little bit to make state more explicit and type-safe:allows to change the behaviour of actor on the fly . In our case we either wait foror for- but never both. But where did the state (reference) go? Well, it's cleverly hidden.method takesas parameter and returns a. This function closes over that parameter, thus actor-global variable is no longer necessary. OK, now we are ready to implement 1 second timeout when waiting forAfter sendingwe immediately schedule sendingmessage to ourselves after precisely one second. Then we go into. Ifarrives we cancel scheduledand sendinstead. However if we first receivedit means one second elapsed. So we forwardback to the client. Is it just me or maybe such a simple task should not require that amount of code?Moreover please notice that ouris not capable of handling more than one client at a time. Oncewas received no more clients are allowed untiloris sent back. Seems quite limiting.Another approach to the very same problem is employingpattern and futures. Suddenly the code becomes much shorter and easier to read:That's it! Weby sendingwhen response arrives we reply to the client. In case it was aplaceholder stands forbut we don't really care) we send. If it was a(wheremost probably holdsthrown after one second without reply) we forward. There is one enormous trap in this code. In both success and failure callbacks we can't usedirectly because these pieces of code can be executed much later by another thread.'s value is transient and by the timearrives it might point to any other actor that happened to send us something. Thus we have to keep originalinlocal variable and capture that one instead.If you find this annoying you might play withpattern:Same as before we(synonym tomethod)with a timeout. If correct reply arrives we map it to. If instead future ends with exception we recover from it by mapping it tomessage. No matter which "branch" was exercised the result istoYou should ask yourself a question: why code above is fine despite usingwhile the previous one would have been broken? If you look closely at the declarations you'll notice thattakes anby value, not by name. This means thatis evaluated immediately when the expression is executed - not later when replies return. We are walking on a thin ice here so please be careful when making such assumptions.Actors are lightweight so why not create one just for the sake of a single health check? This throw-away actor would be responsible for communicating withand pushing reply back to the client. The only responsibility ofwould be to create an instance of this one time actor:is quite simple and similar to the very first solution:When the actor is created we sendtobut also schedule timeout message. Now we wait either foror for timeouted. In both cases we stop ourselves in the end becauseis no longer needed. Of coursecan create multiple independents at the same time.This solution combines simplicity and purity of the first one but is robust as the second one. Of course it also requires most code. It's up to you which technique you employ in real life use cases. BTW after writing this article I came across Ask, Tell and Per-request Actors which touches the same problem and introduces similar approaches. Definitely look at it as well!