There are times when an actor’s initialization takes time. You can argue that an actor should be initialized on construction, and as such cannot be in a state of limbo. This is a fine goal to achieve but it’s not always possible or, if it is, the solution can be worse than the original problem. For example, if you Actor restarts, it may need to read fresh information from the database;
however, if it was constructed with the information from the database, then it won’t receive fresh data. To do that, you’d have to add a layer of indirection to the hierarchy, and let the parent read the new information and then recon- struct the child, but really, what’s the difference? It’s just a more complex way of doing the same thing.
We saw a flavour of this when we created theFlyingBehaviourFSM;
it had to acquire the controls as well as seed a heading indication and an altitude indication before it could move on to actually flying the plane.
Things get more interesting when the actor is visible to the outside world and is expected to handle requests. If the actor isn’t initialized to the point where it actuallycanhandle those requests, then what do you do? Unfor- tunately, the answer to that question is rather dependent on your problem domain, but we’ll cover some solutions here.
However, I must make a key point clear: the bootstrapping algorithm takes place at the same time when other entities are making requests. That’s
just the nature of concurrency. During this time, it is very important to ensure that the incoming requests have a deterministic outcome. If the bootstrapping fails, these messages shouldn’t just go to the dead letter office, unless the client is happy to have that happen. If it’s not happy to have that occur, then we’re back to handling response timeouts to requests, which we just covered in a fair bit. Assuming you don’t want to have your serving actor ignore the request, then it needs to have some sort of deterministic, client-facing behaviour. It’s this sort of thing that we’re going to talk about now.
Denial
Denial is the simplest mechanism to deal with this problem. Your server is unable to handle the request right now, so you give an error back to the client and tell it to try again later. For example, let’s say that the actor in question implements a web service, but during its initialization, it must suck up some data from an external data store. Until its initialization is complete, it will send clients a500HTTP error.
class WaitForInit extends Actor { def uninitialized: Receive = {
case DBResults(results) =>
context.become(initialized(results)) case HTTPRequest(req) =>
req.INTERNAL_ERROR("System not ready. Try again soon.") }
def initialized(data: Map[String, String]): Receive = { case HTTPRequest(req) =>
// dispatch request and fulfill }
def receive = uninitialized }
We simply start in the uninitialized state, which sends errors to all HTTP requests. Once the database information is in, it can become the initialized state and handle requests properly.
Stashing
Akka ships atraitthat you can mix into your actors and lets you stash mes- sages away so that you can process them later. We can use this functionality to save messages while the actor is being bootstrapped.
import akka.actor.Stash
class StashingActor extends Actor with Stash { def uninitialized: Receive = {
case DBResults(results) =>
// Unstash everything since the behaviour we're about to // 'become' will be able to handle whatever was stashed // away
unstashAll()
context.become(initialized(results)) case HTTPRequest(_) =>
// Can't handle it now. Stash it away.
stash() }
def initialized(data: Map[String, String]): Receive = { case HTTPRequest(req) =>
// dispatch request and fulfill req.OK("Here you go")
}
def receive = uninitialized }
When we switch to theinitializedbehaviour, the messages that we stashed away will be processed.
Now, with that said, there are timeouts to consider. You probably can’t just keep messages stashed forever—people will eventually want a result, and you can’t just suck up an infinite amount of memory either. These are things you need to consider when you’re stashing messages.
Caveats
You can’t use the stash traitwithout a particular type of mailbox. This means you need to create a configuration for a dispatcher that specifies the
UnboundedDequeBasedMailboxfor itsmailbox-type, like this:
zzz.akka.investigation.stash-dispatcher {
mailbox-type = "akka.dispatch.UnboundedDequeBasedMailbox"
}
Then you need to construct the actor using the appropriate method on the
Propsclass:
val a = system.actorOf(Props[StashingActor].withDispatcher(
"zzz.akka.investigation.stash-dispatcher"))
You need to use this specific type of mailbox because the unstashed messages are prepended to the mailbox, which is an expensive operation if you’ve only got a queue to work with. A deque provides very fast prepend functionality.
Override ofpreRestart()
The stash traitoverrides preRestart, which makes it sensitive to where you mix it in; it must be mixed in before anything else that overrides
preRestart. For example:
class SomeMix { this: Actor =>
override def preRestart() { ...
}
// You can do this:
class WithStashed extends Actor with Stash with SomeMix // But you CANNOT do this:
class WithStashed extends Actor with SomeMix with Stash
Regarding restarts
The stashed messages do not survive an actor restart. Generally speaking, this should be perfectly fine for most cases where you might want to stash, but you need to be aware that a restart of your actor is going to empty your stash. You may need to put sometry/catchblocks in your code to ensure that the actor doesn’t restart when you don’t want it to.
Note
You might want to forget that you just read that. I know we’re all big boys and girls and we know what we’re doing, but think about this for a moment. . . If you subvert the restart semantics of the actor and are in a bad state that causes you to throw exceptions, you might just end up stashing until you run out of memory.
If you restart, you’re going to lose your stash, and everything’s going to time out for the clients—but if they’re smart, and implemented properly, they know what to do in these cases.
Use the wholetry/catchthing with great care and make sure that you’re solving that particular problem with your eyeswide open.