Search code examples
scalaakkaactor

Don't immediately stop child Akka actors when parent actor stopped


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.


Solution

  • 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, publishing Terminated 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.