Rerun Me
Akka Notes – Actor Supervision
Failures are more like a feature among distributed systems. And with Akka’s let it crash fault-tolerance model, you could achieve a clear separation between your business logic and your failure handling logic (supervision logic). All with very little effort. It’s pretty amazing. This is the topic of our discussion now.
ACTOR SUPERVISION
Imagine a method call stack and the top-most method in your stack throws an Exception. What could be done by the methods down the stack?
- The exception could be caught and handled in order to recover
- The exception could be caught, maybe logged and kept quiet.
- The methods down the stack could also choose to duck the exception completely (or maybe caught and rethrown)
Imagine if all the methods until the main method don’t handle the exception. In that case, the program exits after writing an essay for an exception to the console.
TESTIMONIAL: NETTIKASINO PARAS PALAUTUSPROSENTTI
“In online gaming and casino business, clear processes are especially important. That is where rerun.me & Akka Notes have proven to be extremely useful for us too.” – C. Von Max, Project Manager, Nettikasino Paras Palautusprosentti
Akka Notes – DeathWatch
When we talked about Actor lifecycle, we saw that Actors could be stopped by various means (using ActorSystem.stop or ActorContext.stop or sending a PoisonPill
– there’s also the Kill
and the gracefulStop
).
Whatever reason an Actor dies, there are cases when a few other actors in the system would like to know about it. Let’s take a trivial example of an Actor who talks to a database – let’s call it a RepositoryActor
. For obvious reasons, there would be few other actors in the system who would be sending message to this RepositoryActor
. These “interested” Actors would like to keep an eye
on or watch
this Actor if it goes down. Now, that in Actor terms is called DeathWatch
. And the methods to watch
and unwatch
over this is intuitively ActorContext.watch
and ActorContext.unwatch
. If watched, the watchers would receive a Terminated
message from the stopped Actor which they could comfortably add in to their receive
function.
Unlike Supervision, where there is a strict enforcement of parent-child hierarchy, any Actor could watch
any other Actor in the ActorSystem.
Akka Notes – Child Actors and ActorPath
Actors are completely hierarchical. Whatever Actors that you create HAS to be a child of some other Actor.
Let’s analyze that a bit :
PATH
Say, we create an ActorRef using ActorSystem.actorOf
and try to print it’s path
.
val actorSystem=ActorSystem("SupervisionActorSystem")
val actorRef=actorSystem.actorOf(Props[BasicLifecycleLoggingTeacherActor])
println (actorRef.path)
As you see, a path looks very similar to a file path in a file system.
akka
here is fixed because all these are addresses of Akka Actors – more like file://
or http://
prefix (nothing to do with protocol though).
- The
SupervisionActorSystem
is just the name of your ActorSystem that you created.
- We’ll talk about the
user
in the next section.
- The
$a
is the name of your Actor that the system generated for you. How would you like if your operating system generated random file names for your files? You’d obviously hate it because you would want to refer to that name in future. So, let’s give it a proper meaningful name to it :
val actorRef=actorSystem.actorOf(Props[BasicLifecycleLoggingTeacherActor], "teacherActor")
println (actorRef.path)
That’s it. Now, the path actually makes sense.
Akka Notes – Actor Lifecycle – Basic
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
the method gets called back next. Here, you could initialize resources that you would like to clean-up in postStop
- 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 that 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 constructors, that isn’t possible.
Akka Notes – ActorSystem (Configuration and Scheduling)
As we saw from our previous posts, we could create an Actor using the actorOf
method of the ActorSystem
. There’s actually much more you could do with ActorSystem. We’ll touch upon just the Configuration and the Scheduling bit in this write-up
Let’s look at the subsets of methods available in the ActorSystem.
Remember the application.conf
file we used for configuring our log level in the previous write-up? This configuration file is just like those .properties
files in Java applications and much more. We’ll be soon seeing how we could use this configuration file to customize our dispatchers, mailboxes etc. (I am not even closely doing justice to the power of the typesafe config. Please go through some examples to really appreciate its awesomeness)
So, when we create the ActorSystem using the ActorSystem object’s apply
method without specifying any configuration, it looks out for application.conf
, application.json
and application.properties
in the root of the classpath and loads them automatically.
val system=ActorSystem("UniversityMessagingSystem")
is the same as
val system=ActorSystem("UniversityMessagingSystem", ConfigFactory.load())
To provide evidence to that argument, check out the apply method in ActorSystem.scala
def apply(name: String, config: Option[Config] = None, classLoader: Option[ClassLoader] = None, defaultExecutionContext: Option[ExecutionContext] = None): ActorSystem = {
val cl = classLoader.getOrElse(findClassLoader())
val appConfig = config.getOrElse(ConfigFactory.load(cl))
new ActorSystemImpl(name, appConfig, cl, defaultExecutionContext).start()
}
Akka Notes – Actor Messaging – Request and Response
The last time when we saw Actor messaging, we saw how fire-n-forget messages are sent (Meaning, we just send a message to the Actor but don’t expect a response from the Actor).
Technically, we fire messages to Actors for their side-effects ALL THE TIME. It is by design. Other than not responding, the target Actor could ALSO do the following with that message –
- Send a response back to the sender (in our case, the
TeacherActor
would respond with a quote back to the StudentActor
OR
- Forward a response back to some other Actor who might be the intended audience which in turn might respond/forward/have a side-effect. Routers and Supervisors are examples of those cases. (we’ll look at them very soon)
Akka Notes – Logging and Testing Actors
In the first two parts (one, two), we briefly talked about Actors and how messaging works. In this part, let’s look at fixing up Logging and Testing our TeacherActor
.
RECAP
This is how our Actor from the previous part looked like :
class TeacherActor extends Actor {
val quotes = List(
"Moderation is for cowards",
"Anything worth doing is worth overdoing",
"The trouble is you think you have time",
"You never gonna know if you never even try")
def receive = {
case QuoteRequest => {
import util.Random
val quoteResponse=QuoteResponse(quotes(Random.nextInt(quotes.size)))
println (quoteResponse)
}
}
}
In this first part of Actor Messaging, we’ll create the Teacher Actor and instead of the Student Actor, we’ll use a main program called StudentSimulatorApp
.
Revisiting Student-Teacher in Detail
Let’s for now consider the message sent by the StudentSimulatorApp to the TeacherActor alone. When I say StudentSimulatorApp
, I just mean a normal main program.
I prefer not to talk about how Java concurrency API and their collections made it better and easier because I am sure if you are here, you probably needed more control over the sub-tasks or simply because you don’t like to write locks and synchronized blocks and would prefer a higher level of abstraction.In this series of Akka Notes, we would go through simple Akka examples to explore the various features that we have in the toolkit.
What are Actors?
Akka’s Actors follow the Actor Model (duh!).
Treat Actors like People. People who don’t talk to each other in person. They just talk through mails.
Let’s expand on that a bit.
1. Messaging
Consider two persons – A wise Teacher and Student. The Student sends a mail every morning to the Teacher and the wise Teacher sends a wise quote back.
Points to note :
- The student sends a mail. Once sent, the mail couldn’t be edited. Talk about natural immutability.
- The Teacher checks his mailbox when he wishes to do so.
- The Teacher also sends a mail back (immutable again).
- The student checks the mailbox at his own time.
- The student doesn’t wait for the reply. (no blocking)
That pretty much sums up the basic block of the Actor Model – passing messages.
So, what’s new about quicksort?Well, nothing except that I figured just now (after 2 damn years of release of Java 7) that the Quicksort implementation of the Arrays.sort
has been replaced with a variant called Dual-Pivot QuickSort. This thread is not only awesome for this reason but also how humble Jon Bentley and Joshua Bloch really are.What did I do next?Just like everybody else, I wanted to implement it and do some benchmarking – against some 10 million integers (random and duplicate).Oddly enough, I found the following results :Random Data :
- Quick Sort Basic : 1222 millis
- Quick Sort 3 Way : 1295 millis (seriously !!)
- Quick Sort Dual Pivot : 1066 millis
Duplicate Data :
- Quick Sort Basic : 378 millis
- Quick Sort 3 Way : 15 millis
- Quick Sort Dual Pivot : 6 millis
Stupid Question 1 :
I am afraid that I am missing something in the implementation of 3-way partition. Across several runs against random inputs (of 10 million) numbers, I could see that the single pivot always performs better (although the difference is less than 100 milliseconds for 10 million numbers).
I understand that the whole purpose of making the 3-way Quicksort as the default Quicksort until now is that it does not give 0(n^2) performance on duplicate keys – which is very evident when I run it against duplicate input. But is it true that for the sake of handling duplicate data, a small penalty is taken by 3-way? Or is my implementation bad?
Stupid Question 2
My Dual Pivot implementation (link below) does not handle duplicates well. It takes a sweet forever (0(n^2)) to execute. Is there a good way to avoid this? Referring to the Arrays.sort
implementation, I figured out that ascending sequence and duplicates are eliminated well before the actual sorting is done. So, as a dirty fix, if the pivots are equal I fast-forward the lowerIndex until it is different than pivot2. Is this a fair implementation?
else if (pivot1==pivot2){
while (pivot1==pivot2 && lowIndex<highIndex){
lowIndex++;
pivot1=input[lowIndex];
}
}
Given a Knapsack of a maximum capacity of W and N items each with its own value and weight, throw in items inside the Knapsack such that the final contents has the maximum value. Yikes !!!Here’s the general way the problem is explained – Consider a thief gets into a home to rob and he carries a knapsack. There are fixed number of items in the home – each with its own weight and value – Jewellery, with less weight and highest value vs tables, with less value but a lot heavy. To add fuel to the fire, the thief has an old knapsack which has limited capacity. Obviously, he can’t split the table into half or jewellery into 3/4ths. He either takes it or leaves it.Example :
Knapsack Max weight : W = 10 (units)
Total items : N = 4
Values of items : v[] = {10, 40, 30, 50}
Weight of items : w[] = {5, 4, 6, 3}
A cursory look at the example data tells us that the max value that we could accommodate with the limit of max weight of 10 is 50 + 40 = 90 with a weight of 7.
APPROACH
The way this is optimally solved is using dynamic programming – solving for smaller sets of knapsack problems and then expanding them for the bigger problem.
Let’s build an Item x Weight array called V (Value array)
V[N][W] = 4 rows * 10 columns
Each of the values in this matrix represent a smaller Knapsack problem.
Base case 1 : Let’s take the case of 0th column. It just means that the knapsack has 0 capacity. What can you hold in them? Nothing. So, let’s fill them up all with 0s
Base case 2 : Let’s take the case of 0 row. It just means that there are no items in the house. What do you do hold in your knapsack if there are no items. Nothing again !!! All zeroes.
SOLUTION
1) Now, let’s start filling in the array row-wise. What does row 1 and column 1 mean? That given the first item (row), can you accommodate it in the knapsack with capacity 1 (column). Nope. The weight of the first item is 5. So, let’s fill in 0. In fact, we wouldn’t be able to fill in anything until we reach the column 5 (weight 5).
2) Once we reach column 5 (which represents weight 5) on the first row, it means that we could accommodate item 1. Let’s fill in 10 there (remember, this is a Value array)
3) Moving on, for weight 6 (column 6), can we accommodate anything else with the remaining weight of 1 (weight – weight of this item => 6 – 5). Hey, remember, we are on the first item. So, it is kind of intuitive that the rest of the row will just be the same value too since we are unable to add in any other item for that extra weight that we have got.
4) So, the next interesting thing happens when we reach the column 4 in third row. The current running weight is 4.
We should check for the following cases.
- Can we accommodate Item 2 – Yes, we can. Item 2’s weight is 4.
- Is the value for the current weight is higher without Item 2? – Check the previous row for the same weight. Nope. the previous row* has 0 in it, since we were not able able accommodate Item 1 in weight 4.
- Can we accommodate two items in the same weight so that we could maximize the value? – Nope. The remaining weight after deducting the Item2’s weight is 0.