Akka Typed: How to shut down Akka actors

Akka Actors Problem: You want to see the proper way to shut down Akka Typed actors.

(Note: This tutorial is written for Akka 2.6.)

Solution

There are two main ways to shut down Akka Typed actors:

  • Send them a “graceful shutdown” message, and let them shut themselves down by calling Behaviors.stopped

  • As a parent, stop a child actor with actorContext.stop(actorRef)

Actors that are being stopped can also handle a PostStopped signal they receive when they are being shut down by the Akka system.

These behaviors are shown in the following code. First, create a little Utils object that we can use as a timer:

object Utils {
    var startTime = 0L
    def time = System.currentTimeMillis - startTime
}

Child messages

In the code that follows I’ll create an OOP-style child actor named OopChild, and an FP-style child actor named FpChild, so the next thing I do is create some messages outside of their objects that both actors can use:

// messages that both children can use
sealed trait MessageToChild
final case object GracefulShutdown extends MessageToChild
final case object HelloOopChild extends MessageToChild

An OOP-style actor

Next, create an OOP-style actor named OopChild. I introduced this style in [oop-actor-intro], and I’ve added comments to the code to explain how it works:

object OopChild {
    // the factory/constructor method
    def apply(): Behavior[MessageToChild] =
        Behaviors.setup(context => new OopChild(context))
}

private class OopChild(context: ActorContext[MessageToChild])
extends AbstractBehavior[MessageToChild](context) {
  println(s"${Utils.time}: OopChild is started.")

  // onMessage lets us handle normal messages
  override def onMessage(message: MessageToChild): Behavior[MessageToChild] = {
      message match {
          case HelloOopChild =>
              println(s"${Utils.time}: Hi, I’m OopChild.")
              Behaviors.same
      }
  }

  // onSignal lets us handle “signals” from the ActorSystem. I’m only
  // interested in PostStop, but there a few other signals can be handled.
  override def onSignal: PartialFunction[Signal, Behavior[MessageToChild]] = {
      case PostStop =>
          println(s"${Utils.time}: OopChild: Got the PostStop signal.")
          // can’t use Behaviors.stopped here because you’re already stopped
          println(s"${Utils.time}: OopChild could have done some cleanup here")
          this
  }

}

The reason I create an OOP-style actor first is because — at least for me — I find the code easier to read. It’s very clear that onMessage and onSignal are methods inside an actor class.

This is the first time I’ve showed the onSignal method, and it lets you handle lifecycle “signals” that are sent to an actor by the Akka system. There are other signals an actor can receive, but for the purposes of showing how actors are shut down, PostStop is the only signal I’m interested in.

Note: Akka Typed lifecycle events

If you’ve written code for Android, Flutter, Drupal, or other systems, this is just like handling lifecycle events with those tools.

An FP-style actor

Next, here’s the source code for an FP-style actor. It’s similar to the OOP-style actor in that it also handles the PostStop message, but its syntax for doing so is different. Also, this actor handles a GracefulShutdown message to shut itself down. I’ve added comments to it to explain its behavior:

import Utils.time

object FpChild {

  // the usual factory method
  def apply(): Behavior[MessageToChild] = {
      // i added output so you can see when `apply` is called
      println(s"$time: FpChild: `apply` is entered.")
      Behaviors.receive[MessageToChild] { (context, message) =>
      message match {
          // in the Guardian that follows, you’ll see that this actor is
          // shut down with the use of this message
          case GracefulShutdown =>
              println(s"$time: FpChild: Initiating graceful shutdown.")
              Behaviors.stopped { () =>
                  println(s"$time: FpChild: could have done some cleanup here")
              }
      }
  }.receiveSignal {
          // `receiveSignal` is the equivalent of the OopChild’s `onSignal`
          // method. handle the PostStop signal here.
          case (context, PostStop) =>
              println(s"${Utils.time}: FpChild: Got the PostStop signal.")
              Behaviors.same
      }
  }

}

The Guardian actor

Next, here’s the code for a “Guardian” actor. It will be created by my App, and it’s responsible for (a) creating the two child actors, and then (b) shutting down those actors. Again I’ve added comments and println statements to explain what’s happening:

import Utils.time

object Guardian {

    // the messages this actor handles
    sealed trait MessageToGuardian
    final case object StopChild1 extends MessageToGuardian
    final case object StopChild2 extends MessageToGuardian
    final case object ShutDownTheGuardian extends MessageToGuardian

    // the usual factory method. this line of code is long,
    // so it’s wrapped onto two lines here.
    def apply(): Behavior[MessageToGuardian] =
    Behaviors.setup { context: ActorContext[MessageToGuardian] =>

        println(s"$time: Guardian: `apply` is entered.")

        // create two children
        val fpChild: ActorRef[MessageToChild] = context.spawn(
            FpChild(), "FpChild"
        )
        val oopChild: ActorRef[MessageToChild] = context.spawn(
            OopChild(), "OopChild"
        )

        // handle the messages we receive. the main purpose of this code is
        // to show the two different ways to shut down child actors.
        Behaviors.receiveMessage { message: MessageToGuardian =>
            message match {
                case StopChild1 =>
                    println(s"$time: Guardian:" +
                        "Sending GracefulShutdown message to fpChild.")
                    fpChild ! GracefulShutdown
                    Behaviors.same
                case StopChild2 =>
                    println(s"$time: Guardian:" +
                        "Stopping oopChild with `context.stop`.")
                    context.stop(oopChild)
                    Behaviors.same
                case ShutDownTheGuardian =>
                    println(s"$time: Guardian: Received a shutdown message.")
                    Behaviors.stopped
            }
        }
    }
}

An Akka Typed example application

With all of that code in place, here’s the source code for an App that demonstrates how to shut down actors:

object GracefulShutdownApp extends App {

    // start the timer
    Utils.startTime = System.currentTimeMillis
    println(s"${Utils.time}: The App is started.")

    // start the ActorSystem, which also starts the FpChild and OopChild actors
    val actorSystem: ActorSystem[Guardian.MessageToGuardian] = ActorSystem(
        Guardian(),
        "Guardian"
    )

    // after a brief delay, tell the Guardian to stop the children
    Thread.sleep(100)
    actorSystem ! Guardian.StopChild1
    actorSystem ! Guardian.StopChild2

    // after another delay, tell the Guardian to stop itself
    Thread.sleep(100)
    actorSystem ! Guardian.ShutDownTheGuardian

    // shut down the ActorSystem
    Thread.sleep(100)
    actorSystem.terminate()
}

With some Akka logging information removed and a few blank lines added, the output of this App looks like this:

0: The App is started.

336: Guardian: `apply` is entered.
337: FpChild: `apply` is entered.
340: OopChild is started.

433: Guardian: Sending GracefulShutdown message to fpChild.
434: Guardian: Stopping oopChild with `context.stop`.

434: FpChild: Initiating graceful shutdown.
438: FpChild: Got the PostStop signal.
438: FpChild could have done some cleanup here ...

438: OopChild: Got the PostStop signal.
438: OopChild could have done some cleanup here ...

535: Guardian: Received a shutdown message.

If you look back through the code for the three actors and the App, hopefully that output will match what you expected to see.

Discussion

Possibly the most important thing to see in these examples — besides the proper syntax for the two approaches — is that both the OopChild and FpChild receive the PostStop signal. This means that you can clean up any open resources you have — database connections, HTTP connections, files, etc. — before your actor shuts down.

Because of the way the code is implemented, FpChild can do any necessary cleanup work in two places, (a) when it receives the GracefulShutdown message, and (b) in receiveSignal:

case GracefulShutdown =>
    Behaviors.stopped { () =>
        // (a) can do cleanup work here
    }

receiveSignal {
    case (context, PostStop) =>
        // (b) can do cleanup work here
        Behaviors.same
}

Because I wrote OopChild without handling a graceful shutdown message, the only place it can do its cleanup work is in the PostStop case inside onSignal:

override def onSignal: PartialFunction[Signal, Behavior[MessageToChild]] = {
    case PostStop =>
        // can do cleanup work here.
        // can’t use Behaviors.stopped here because you’re already stopped.
        this
}

... this post is sponsored by my books ...
 

See Also

The Akka Typed Lifecycle page discusses the lifecycle signals that actors can receive, include PostStop, PreRestart, and Terminated.