Akka/Scala: How to monitor the death of an Actor with “watch”

This is an excerpt from the Scala Cookbook (partially modified for the internet). This is Recipe 13.8, “Monitoring the death of an Akka Actor with ‘watch’”.

Problem

In a Scala application, you want an Akka actor to be notified when another actor dies.

Solution

Use the watch method of an actor’s context object to declare that the actor should be notified when an actor it’s monitoring is stopped.

In the following code snippet, the Parent actor creates an actor instance named kenny, and then declares that it wants to “watch” kenny:

class Parent extends Actor {
    val kenny = context.actorOf(Props[Kenny], name = "Kenny")
    context.watch(kenny)
    // more code here ...

(Technically, kenny is an ActorRef instance, but it’s simpler to say “actor.”)

If kenny is killed or stopped, the Parent actor is sent a Terminated(kenny) message. This complete example demonstrates the approach:

package actortests.deathwatch

import akka.actor._

class Kenny extends Actor {
    def receive = {
        case _ => println("Kenny received a message")
    }
}

class Parent extends Actor {
    // start Kenny as a child, then keep an eye on it
    val kenny = context.actorOf(Props[Kenny], name = "Kenny")
    context.watch(kenny)
    def receive = {
        case Terminated(kenny) => println("OMG, they killed Kenny")
        case _ => println("Parent received a message")
  }
}

object DeathWatchTest extends App {

      // create the ActorSystem instance
      val system = ActorSystem("DeathWatchTest")

      // create the Parent that will create Kenny
      val parent = system.actorOf(Props[Parent], name = "Parent")

      // lookup kenny, then kill it
      val kenny = system.actorSelection("/user/Parent/Kenny")
      kenny ! PoisonPill
      Thread.sleep(5000)
      println("calling system.shutdown")
      system.shutdown
}

When this code is run, the following output is printed:

OMG, they killed Kenny
calling system.shutdown

Discussion

Using the watch method lets an actor be notified when another actor is stopped (such as with the PoisonPill message), or if it’s killed with a Kill message or gracefulStop. This can let the watching actor handle the situation, as desired.

An important thing to understand is that if the Kenny actor throws an exception, this doesn’t kill it. Instead it will be restarted. You can confirm this by changing the Kenny actor code to this:

case object Explode

class Kenny extends Actor {
    def receive = {
        case Explode => throw new Exception("Boom!")
        case _ => println("Kenny received a message")
    }
    override def preStart { println("kenny: preStart") }
    override def postStop { println("kenny: postStop") }
    override def preRestart(reason: Throwable, message: Option[Any]) {
        println("kenny: preRestart")
        super.preRestart(reason, message)
    }
    override def postRestart(reason: Throwable) {
        println("kenny: postRestart")
        super.postRestart(reason)
    }
}

Also, change this line of code in the DeathWatchTest object:

kenny ! PoisonPill

to this:

kenny ! Explode

When you run this code, in addition to the error messages that are printed because of the exception, you’ll also see this output:

kenny: preRestart
kenny: postStop
kenny: postRestart
kenny: preStart
calling system.shutdown
kenny: postStop

What you won’t see is the “OMG, they killed Kenny” message from the Parent actor, because the exception didn’t kill kenny, it just forced kenny to be automatically restarted. You can verify that kenny is restarted after it receives the explode message by sending it another message:

kenny ! "Hello?"

It will respond by printing the “Kenny received a message” string in the default _ case of its receive method.

Looking up actors

This example also showed one way to look up an actor:

val kenny = system.actorSelection("/user/Parent/Kenny")

As shown, you look up actors with the actorSelection method, and can specify a full path to the actor in the manner shown. The actorSelection method is available on an ActorSystem instance and on the context object in an Actor instance.

You can also look up actors using a relative path. If kenny had a sibling actor, it could have looked up kenny using its own context, like this:

// in a sibling actor
val kenny = context.actorSelection("../Kenny")
You can also use various implementations of the actorFor method to look up actors.

The kenny instance could be looked up from the DeathWatchTest object in these ways:

val kenny = system.actorFor("akka://DeathWatchTest/user/Parent/Kenny")
val kenny = system.actorFor(Seq("user", "Parent", "Kenny"))

It could also be looked up from a sibling like this:

val kenny = system.actorFor(Seq("..", "Kenny"))