Scala
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Scala
-
Section 4: Functional Scala
-
Section 5: Collections in Depth
-
Section 6: Type System
-
Section 7: Concurrency and Ecosystem
-
Section 8: Practical Projects
-
Section 9: Interview Practice
-
Section 10: Data Structures and Algorithms in Scala
-
Section 11: More Practice Exercises
-
Section 12: Advanced Functional Patterns
-
Section 13: More Ecosystem
-
Section 14: Scala Collections Library Deep Dive
-
Section 15: Scala Standard Library Deep Dive
-
Section 16: Akka Ecosystem Deep Dive
-
Section 17: Cats and Cats Effect Deep Dive
-
Section 18: Play Framework Deep Dive
-
Section 19: Apache Spark with Scala Deep Dive
-
Section 20: Scala Build Tools Deep Dive
-
Section 21: Scala 3 Specific Features
-
90: Union and Intersection Types
-
Section 22: Scala Testing Deep Dive
-
Section 23: Functional Domain Modeling
-
Section 24: More Data Structures and Algorithms in Scala
-
Section 25: Scala for Data Engineering
-
Section 26: More Practical Projects
-
Section 27: More Interview and Review
-
Section 28: ZIO Ecosystem Deep Dive
-
Section 29: Scala for Machine Learning
-
Section 30: Scala Microservices Architecture
-
Section 31: Scala Type System Deep Dive
-
Section 32: More Practice and Drills
-
Section 33: Scala Performance Deep Dive
-
Section 34: Scala Ecosystem Tooling
-
Section 35: Scala for Reactive Systems
-
Section 36: More Real-World Case Studies
-
Section 37: Scala for Financial Systems
-
Section 38: Scala GraphQL and gRPC
-
Section 39: More Final Projects
-
Section 40: More Interview and Final Review
-
Section 41: Scala for Streaming Data
-
Section 42: Scala Security Practices
-
Section 43: More Language Deep Dive
-
Section 44: Scala Command-Line Tools
-
Section 45: Scala Documentation and Style
-
Section 46: Scala Dependency Management
-
Section 47: More Practical Backend Patterns
-
Section 48: Scala for Event-Driven Architecture
-
Section 49: More Practice Drills Round 2
-
Section 50: Scala Compiler Deep Dive
-
Section 51: Scala for Web Frontends
-
Section 52: More Data Engineering Practice
-
Section 53: Scala Observability
-
Section 54: More Advanced Practice Projects
-
Section 55: Scala for Legacy Java Integration
-
Section 56: More Testing Practice
-
Section 57: Final Mastery Review
-
Section 58: Scala History and Ecosystem Context
-
Section 59: More Concurrency Patterns
-
Section 60: Scala for Configuration Management
-
Section 61: More Domain Modeling Practice
66: Akka Actor Lifecycle
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
preStartto print a message simulating the creation of a temporary file (e.g.,"Creating temp file: worker_123.tmp"). - Processing: In the
receiveblock, handle a messageWriteData(content: String)which prints that it is writing to the file. - Cleanup: Override
postStopto print a message simulating the deletion of that temporary file (e.g.,"Deleting temp file: worker_123.tmp"). - Termination: Handle a message
Shutdownthat triggers the actor to stop itself usingcontext.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.
There are no comments for now.