Over a million developers have joined DZone.

Akka Notes: Actor Lifecycle Basics Part 5

· IoT Zone

Access the survey results 'State of Industrial Internet Application Development' to learn about latest challenges, trends and opportunities with Industrial IoT, brought to you in partnership with GE Digital.

(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 thePoisonPill 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 [java.lang.String] 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 the EventStream.

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(classOf[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 [java.lang.String] 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]) 

The IoT Zone is brought to you in partnership with GE Digital.  Discover how IoT developers are using Predix to disrupt traditional industrial development models.

Topics:

Published at DZone with permission of Arun Manivannan, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}