I have an actor p: ParentActor
which I want to do some cleanup jobs when it is stopped by making use of the postStop
method. One of the jobs involves sending a message to a child actor c: ChildActor
. After that, c
and then p
should be stopped. However, once context.stop(p)
has been called c
seems to be immediately stopped and is unable to receive messages.
Below is an example of what I'd like to do:
class ParentActor extends Actor {
...
override def postStop {
logger.info("Shutdown message received. Sending message to child...")
val future = c ? "doThing"
logger.info("Waiting for child to complete task...")
Await.result(future, Duration.Inf)
context.stop(c)
logger.info("Child stopped. Stopping self. Bye!")
context.stop(self)
}
}
Results in error message:
[ERROR] [SetSystem-akka.actor.default-dispatcher-5] [akka://SetSystem/user/ParentActor] Recipient[Actor[akka://SetSystem/user/ParentActor/ChildActor#70868360]] had already been terminated. Sender[Actor[akka://SetSystem/user/ParentActor#662624985]] sent the message of type "java.lang.String".
An alternative would be to send p
a message saying to shutdown, and have the above actions take place as a result of that, but using the built in stopping functionality seems better.
PS. This is a new application so design alternatives are welcome.
When an actor A
is stopped, its children are indeed stopped before A
's postStop
hook is called. The sequence of events when an actor is stopped is as follows (from the official documentation):
Termination of an actor proceeds in two steps: first the actor suspends its mailbox processing and sends a stop command to all its children, then it keeps processing the internal termination notifications from its children until the last one is gone, finally terminating itself (invoking
postStop
, dumping mailbox, publishingTerminated
on the DeathWatch, telling its supervisor).
Overriding the parent's postStop
won't help you because your desired shutdown procedure includes sending a message to a child and waiting for a reply. When the parent is terminated, the child is stopped before the parent's postStop
is run.
As you mentioned, sending a specific message to the ParentActor
to initiate the shutdown is another approach. Something like the following would work:
import akka.pattern.pipe
class ParentActor extends Actor {
...
def receive = {
case Shutdown =>
(c ? DoYourThing).mapTo[ThingIsDone].pipeTo(self)
case ThingIsDone =>
logger.info("Child has finished its task.")
context.stop(self)
case ...
}
}
Note that one should avoid using Await
in an actor. Instead, pipe the result of a Future
to an actor.