Finalize resources used by routes on server shutdown

I have a few unrelated services. Currently these are deployed within a single (HttpApp) server, but this may change in future. Some of these services require long-lived resources, like Slick JDBC backends, whose life span should exceed a single request and that should be closed upon server shutdown.

Creating these resources prior to server startup and passing them into the route factory methods of the services feels wrong, since it leaks lots of knowledge about the service requirements into the server.

Currently I’m using something like

case class RouteWithShutdown(route: Route, shutdown: ShutdownCallback)

to return from the service factory methods. The routes are aggregated/path-prefixed/sealed into a single server route, and the shutdown callbacks are bundled and kept to be called in the #postServerShutdown() callback.

Is there any better pattern for this?

Bonus question: If this approach is ok, what would you use for aggregating the shutdown callbacks to ensure that they’re all called and that exceptions during these calls are kept around for later reporting?

Thanks and best regards,
Patrick

One idea would be to keep those resources in an Akka Extension (to keep their lifecycle bound to the actor system rather than the JVM), and hook into coordinated shutdown for shutting them down when the ActorSystem shuts down.

Thanks a lot for the suggestion! Unfortunately it took me some time to get back to this task, but now I’ve given it a try and it definitely is an improvement over my RYO approach.

Just to make sure I got this right (and, if so, for the benefit of others grappling with similar issues)…

I’m wrapping the CoordinatedShutdown stuff like this:

type ShutdownHook = (String, () => Future[Done]) => Unit
def registerShutdownHook(
    shutdown: CoordinatedShutdown)(
    taskName: String, task: () => Future[Done]): Unit =
  shutdown.addTask(CoordinatedShutdown.PhaseBeforeActorSystemTerminate, taskName)(task)

From the main server route, I create a shutdown hook with

registerShutdownHook(CoordinatedShutdown(system))

and pass it to the service routes which simply call it, passing their cleanup code as the task.

In the server code (based on HttpApp), I invoke the coordinated shutdown in the postServerShutdown hook, instead of explicitly terminating the system. (By default, coordinated shutdown will terminate the actor system, and termination of the actor system will not trigger the coordinated shutdown sequence!)

CoordinatedShutdown(system).run(CoordinatedShutdown.UnknownReason)

That leaves the test cases. Testkit certainly doesn’t like to have its actor system terminated from the outside, so this needs to be configured in the tests’ application.conf as described here.

akka.coordinated-shutdown.terminate-actor-system = off
akka.coordinated-shutdown.run-by-jvm-shutdown-hook = off

I hope this makes sense. At least it seems to work for me. Thanks again!

1 Like

Unfortunately this success report was a bit premature…

While the life cycles of the actor system and the route(s) match in production, in the test scenario the test kit will provide an actor system per test suite class, while routes (and their resources) should be instantiated and cleaned up per test.

I’ve ended up just reverting the flow of my first approach: Instead of returning shutdown hooks from the route factories, I’m passing around a shutdown hook registry. Just for the kicks, I’m using a CoordinatedShutdown based implementation in the production case, but in principle it could as well be the RYO variant I’m using in the tests now.