Scala

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.
 
 
 
 
 
 

  1. Just like any other regular class, we have a Constructor
  2. The preStart method gets called back next. Here, you could initialize resources that you would like to clean-up in postStop
  3. 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 DeadLetters 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.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button