Table of Contents
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"))
this post is sponsored by my books: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |