Akka Notes – Actor Lifecycle – Basic – 5
(Please note that this lifecycle write-up does not cover the preRestart
or the postRestart
methods. We’ll talk about them when we discuss supervision)
The basic Actor lifecycle is very much intuitive. You could actually compare the basic Actor lifecycle with a Java servlet lifecycle with one special difference.
- Just like any other regular class, we have a Constructor
- The
preStart
method gets called back next. Here, you could initialize resources that you would like to clean-up inpostStop
- The “servicing” or the message handling by the
receive
method occupies the major chunk of time and that happens in between.
Let’s look at a simple actor which prints the lifecycle.
Dumb Lifecycle Actor
package me.rerun.akkanotes.lifecycle import akka.actor.{ActorLogging, Actor} import akka.event.LoggingReceive class BasicLifecycleLoggingActor extends Actor with ActorLogging{ log.info ("Inside BasicLifecycleLoggingActor Constructor") log.info (context.self.toString()) override def preStart() ={ log.info("Inside the preStart method of BasicLifecycleLoggingActor") } def receive = LoggingReceive{ case "hello" => log.info ("hello") } override def postStop()={ log.info ("Inside postStop method of BasicLifecycleLoggingActor") } }
App
The LifecycleApp
just initiates, sends a message to the Actor and shuts down the ActorSystem.
import akka.actor.{ActorSystem, Props} object LifecycleApp extends App{ val actorSystem=ActorSystem("LifecycleActorSystem") val lifecycleActor=actorSystem.actorOf(Props[BasicLifecycleLoggingActor],"lifecycleActor") lifecycleActor!"hello" //wait for a couple of seconds before shutdown Thread.sleep(2000) actorSystem.shutdown() }
Output
Inside BasicLifecycleLoggingActor Constructor Actor[akka://LifecycleActorSystem/user/lifecycleActor#-2018741361] Inside the preStart method of BasicLifecycleLoggingActor hello Inside postStop method of BasicLifecycleLoggingActor
What’s that special difference between Servlets and the basic Actor lifecycle?
That there is no difference between constructor and preStart in Actor lifecycle – more or less.
The reason why I printed the context.self
in the constructor is this – unlike Servlets, Actors have access to the ActorContext
even inside the constructor. The difference between the preStart and the constructor then becomes very subtle. We’ll revisit the difference while we talk about supervision but if you are curious – calling the preStart
when the Actor restarts (in case of failure) could be controlled. With constructor, that isn’t possible.
When is postStop called?
As we saw from the program, the postStop
gets called when the ActorSystem shuts down. There are a couple of other times when the callback gets invoked too.
1. ActorSystem.stop()
We could stop an Actor using the stop
method of the ActorSystem
and the ActorContext
object LifecycleApp extends App{ val actorSystem=ActorSystem("LifecycleActorSystem") val lifecycleActor=actorSystem.actorOf(Props[BasicLifecycleLoggingActor],"lifecycleActor") actorSystem.stop(lifecycleActor); ... ... }
2. ActorContext.stop
1) Either by way of a message (externally or passing a message to itself)
class BasicLifecycleLoggingActor extends Actor with ActorLogging{ ... ... def receive = LoggingReceive{ case "hello" => log.info ("hello") case "stop" => context.stop(self) }
and
object LifecycleApp extends App{ val actorSystem=ActorSystem("LifecycleActorSystem") val lifecycleActor=actorSystem.actorOf(Props[BasicLifecycleLoggingActor],"lifecycleActor") lifecycleActor!"stop" ... ...
2) OR kill itself for no reason
(this is just for fun. No Actor with an ambition would do this)
class BasicLifecycleLoggingActor extends Actor with ActorLogging{ log.info ("Inside BasicLifecycleLoggingActor Constructor") log.info (context.self.toString()) context.stop(self) ... ...
3. PoisonPill
In the previous example, we passed a message called stop from the LifecycleApp to the Actor. The Actor received that message and killed itself using a context.stop
. We could achieve the same thing by passing a PoisonPill message to the target actor. Please note that the PoisonPill
message, just like the previous stop message gets enqueued in the regular mailbox and will be processed when it turn comes up.
object LifecycleApp extends App{ val actorSystem=ActorSystem("LifecycleActorSystem") val lifecycleActor=actorSystem.actorOf(Props[BasicLifecycleLoggingActor],"lifecycleActor") lifecycleActor!PoisonPill ... ...
TRIVIA
What do I mean regular mailbox? Is there a “special” mailbox too? Yup. There is. And we’ll talk about it when we talk about it when we discuss supervision and system
messages.
Termination
Once the Actor is stopped, it is said to enter into a Terminated
state. The immediate question that would come up to your mind is what would happen to the messages that is sent to an Actor which is already terminated?
Let’s see that :
App
object LifecycleApp extends App{ val actorSystem=ActorSystem("LifecycleActorSystem") val lifecycleActor=actorSystem.actorOf(Props[BasicLifecycleLoggingActor],"lifecycleActor") lifecycleActor!"hello" lifecycleActor!"stop" lifecycleActor!"hello" //Sending message to an Actor which is already stopped }
Actor – Just as before
class BasicLifecycleLoggingActor extends Actor with ActorLogging{ def receive = LoggingReceive{ case "hello" => log.info ("hello") case "stop" => context.stop(self) } }
Output
BasicLifecycleLoggingActor - hello akka.actor.RepointableActorRef - Message from Actor[akka://LifecycleActorSystem/deadLetters] to Actor[akka://LifecycleActorSystem/user/lifecycleActor#-569230546] was not delivered. [1] dead letters encountered. This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'.
From the logs, you see that there are some references of deadletters
. Any message that you send to an Actor that is terminated gets forwarded to the mailbox of an internal Actor called DeadLetterActor.
CURIOUS AS TO WHAT HAPPENS NEXT?
The DeadLetter Actor processes the messages in its mailbox, wraps each message as a DeadLetter and publishes it to theEventStream
.
One other Actor called DeadLetterListener consumes all DeadLetter
s and publishes that as a log message. Check this out.
Remember, when we talked about logging, we saw that all log messages gets published to the EventStream and that we are free to subscribe to that EventStream – just that the subscriber also needs to be an Actor. Let’s try that now.
For our example, we’ll subscribe to the EventStream and watch out for all DeadLetter messages and will print to the console (so much for creativity??!!). Frankly, we are free to do anything from generating an alert, storing it into a database or even feeding into analytics.
SUBSCRIBING TO DEADLETTERS IN EVENTSTREAM
import akka.actor.ActorSystem import akka.actor.Props import akka.actor.PoisonPill import akka.actor.DeadLetter import akka.actor.Actor object LifecycleApp extends App { val actorSystem = ActorSystem("LifecycleActorSystem") val lifecycleActor = actorSystem.actorOf(Props[BasicLifecycleLoggingActor], "lifecycleActor") val deadLetterListener = actorSystem.actorOf(Props[MyCustomDeadLetterListener]) actorSystem.eventStream.subscribe(deadLetterListener, classOf[DeadLetter]) lifecycleActor ! "hello" lifecycleActor ! "stop" lifecycleActor ! "hello" } class MyCustomDeadLetterListener extends Actor { def receive = { case deadLetter: DeadLetter => println(s"FROM CUSTOM LISTENER $deadLetter") } }
Output
164 [LifecycleActorSystem-akka.actor.default-dispatcher-4] INFO BasicLifecycleLoggingActor - hello 167 [LifecycleActorSystem-akka.actor.default-dispatcher-4] INFO akka.actor.RepointableActorRef - Message from Actor[akka://LifecycleActorSystem/deadLetters] to Actor[akka://LifecycleActorSystem/user/lifecycleActor#-782937925] was not delivered. [1] dead letters encountered. This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'. FROM CUSTOM LISTENER DeadLetter(hello,Actor[akka://LifecycleActorSystem/deadLetters],Actor[akka://LifecycleActorSystem/user/lifecycleActor#-782937925])
Reference: | Akka Notes – Actor Lifecycle – Basic – 5 from our JCG partner Arun Manivannan at the Rerun.me blog. |