Skip to Content
Course content

66: Akka Actor Lifecycle

Click on the "Edit" button in the top corner of the screen to edit your slide content.

You've already got the hang of sending messages and managing state in Akka, but in a production system, actors aren't just immortal message-processing machines. They are born, they might crash, and eventually, they have to die. If your actor opens a file handle or a network socket, you can't just let the actor vanish and hope the JVM cleans it up—you need a way to hook into those lifecycle events.

I want to show you how this works by building a UserSessionActor. The goal is simple: when the actor starts, it "loads" a session from a database. When it stops, it needs to "persist" the final state and clear a session lock. If we don't handle this correctly, we end up with "ghost sessions" in our database that never get unlocked.

Setting up the Session Manager

First, let's define the basic structure. I'm using preStart here. This is the perfect spot for initialization because it's called after the actor is created but before it starts processing any messages from its mailbox. I don't want the actor to start answering "What's my session status?" before it's actually loaded the data from the disk.

import akka.actor.{Actor, Props, ActorSystem}

case class LoadSession(userId: String)
case object CheckStatus

class UserSessionActor extends Actor {
  override def preStart(): Unit = {
    println(s"[Lifecycle] Actor starting... Loading session data for user.")
    // In a real app, this would be a future call to a DB
    println(s"[Lifecycle] Session loaded and lock acquired.")
  }

  def receive: Receive = {
    case CheckStatus => 
      println("Session is active and healthy.")
    case "stop" => 
      context.stop(self)
  }
}

The "Leaky Resource" Blunder

Now, here is where I usually trip up when I'm rushing a feature. I'll implement the start logic, verify that the actor responds to messages, and think, "Great, it works." But I'll forget that the actor needs to release that session lock when it's shut down.

If I run the code above and send the "stop" message, the actor terminates. But look at my logs: I see "Session loaded," but I never see "Session lock released." In a high-traffic system, this is a disaster. You'd eventually hit a maximum connection limit or lock out your users because the actors died without cleaning up after themselves.

Tidying up with postStop

To fix this, we use postStop. This hook is called after the actor has processed its last remaining messages and has officially stopped. It's the "destructor" of the Akka world. Even if the actor stops because of a failure or an explicit context.stop(self), postStop will execute.

Let's refine the actor to ensure we don't leave any hanging locks:

class UserSessionActor extends Actor {
  override def preStart(): Unit = {
    println(s"[Lifecycle] Actor starting... Loading session data.")
  }

  override def postStop(): Unit = {
    println(s"[Lifecycle] Actor stopping... Releasing session lock and persisting state.")
    // This is where you'd close your sockets or DB connections
  }

  def receive: Receive = {
    case CheckStatus => 
      println("Session is active.")
    case "stop" => 
      context.stop(self)
  }
}

Now, when you send that "stop" message, the sequence is deterministic: preStart → receive → postStop. It feels like basic object-oriented cleanup, but because actors are asynchronous, having these dedicated hooks is the only way to guarantee your cleanup code runs in the correct order relative to the message processing.

Handling the "Oops" with postRestart

One last thing: what happens if the actor crashes? If you have a supervision strategy that restarts the actor, postStop is called, and then preStart is called again for the new instance. But if you have state that you want to maintain across those restarts, you'd look at postRestart.

I rarely use postRestart these days because I prefer to keep actors stateless or use Akka Persistence, but it's there if you need to re-initialize something specifically after a failure rather than during a cold start. Just remember: preStart is for the very first breath, and postStop is for the final exhale.




📋 Practical Task

Build a TempFileWorker Actor

Create an actor called TempFileWorker that manages a dummy temporary file on the system. Your implementation must satisfy these requirements:

  • Initialization: Override preStart to print a message simulating the creation of a temporary file (e.g., "Creating temp file: worker_123.tmp").
  • Processing: In the receive block, handle a message WriteData(content: String) which prints that it is writing to the file.
  • Cleanup: Override postStop to print a message simulating the deletion of that temporary file (e.g., "Deleting temp file: worker_123.tmp").
  • Termination: Handle a message Shutdown that triggers the actor to stop itself using context.stop(self).

Verify your solution by starting the actor, sending a WriteData message, and then sending the Shutdown message to ensure the cleanup log appears.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.