It’s time to pull the curtain back a little bit so that you have a better under- standing of how the actor model helps you send and route messages. We’ve already covered the actor’s basic entity layout back inSection 5.1, but there’s more to it than that when we start working with more than one actor at a time.
One of the most illuminating things we can see is the true definition of the method for the tell syntax, which we will pull from the Scala implementation of theActorRef, which is calledScalaActorRef:
def ! (message: Any)(implicit sender: ActorRef = null): Unit
What can we see from this declaration?
1. ! is a side-effect function. We know it does something, but it returns
Unit, so whatever it does is a side effect of its execution. This isn’t a surprise. We know that actors don’t compose and that tell syntax enqueues messages.
2. As promised, a message can be anything we like since it can be a subtype ofAny.
3. Every time we call !, there is an implicit ActorRef added to the call, if one exists.
That last point is the one in which we’re really interested. Through the magic of Scala’simplicits, we get a nice clean syntax for sending messages that lets us ignore the plumbing that sets up the reference to the message’s sender. The receiving actor will get a reference to the sender, provided that the sender is actually an actor (as opposed to mainin some of our previous encounters with!).
If we were to look at the Scaladoc forakka.actor.Actor, we would see a nifty little member value there:
implicit val self: ActorRef
When you’re inside an actor, you have an implicit value in scope that can satisfy the second curried parameter of the!method, and this is how the sender is populated when the message is sent. When you send your message,
Akka puts your current actor’s ActorRef and the message itself inside an envelope and that’s what actually gets delivered to the target actor.
You can also see from the declaration that a default value applies to the implicit parameter should nothing be in scope to satisfy it—null. There’s no big surprise to this. . . if a value can’t be found for it then it’s presumed that no such value exists andnull is the only reasonable thing to put in there.
null is, effectively, a sentinel value that Akka uses to understand that the sender’s context is outside of its influence. The message still gets there, but it’s truly a one-way message since there’s nobody to receive any response.
So, if you’re inside an actor and want tonullout the sender, then you can easily specify the sender explicitly:
someActorSomewhere.!("This is a message")(Actor.noSender)
But that’s not exactly beautiful. In this case, it’s much nicer to use the
tellfunction, which is more generally defined on theActorRefas opposed to theScalaActorRef:
// tell is defined as
def tell(msg: Any, sender: ActorRef): Unit // so we can do this
someActorSomewhere.tell("This is a message", Actor.noSender)
Accessing the sender
Inside the receiving actor, we can get the reference to the sender using an aptly named method:
def sender: ActorRef
Look at that again. See it? That’s amethodnot avalue. Not only that, it’s a method that’s defined without parens. This might lead some to believe that it’s equivalent to a value, but that’s not the case. If you really wanted to be dogmatic about it, you could argue that it must be defined assender() since that would be a better indicator that its return value is dependent on some sort of internal state, which can change from moment to moment. But it’s much more pleasing without the parens, don’t ya think?
Thissendermethod gives access to the sender that hitched a ride on the incoming message that the actor is currently processing. But remember that because it’s a method and one that can change its return value (conceivably with every single incoming message), you have to treat it with care.
For example, something like this would be a bad idea:
case SomeMessage =>
context.system.scheduleOnce(5 seconds) { sender ! DelayedResponse
}
It’s quite likely that the value returned from the call tosenderfive sec- onds from now won’t be the value you were hoping for. To make this work out the way you’d like, you need to freeze the value fromsenderin aval:
case SomeMessage =>
val requestor = sender
context.system.scheduleOnce(5 seconds) { requestor ! DelayedResponse
}
Tip
Usingsenderinside of a closure is a textbook way of giving away the keys to the fortress. It’s easy to do, and the only thing you can do to prevent it is to not do it. In short order, you’re going to recognize this sort of mistake very easily and you’ll avoid it without trouble.
Null senders
So what happens when the sender isnull? The quick answer is that we don’t get a dreaded NullPointerException if we try to send a message to it.
Akka usesnullonly as a sentinel value to let the toolkit know that there’s no hitchhiker to add to the message. When this situation occurs, Akka attaches a default sender called thedead letter office, which is a single actor instance per ActorSystemand can be accessed directly from theActorSystemvia thedeadLettersmethod.
This means that you can always use the result from thesendermethod with confidence. It may be that the receiver of your response message is
dead or never existed in the first place, but in either case the message goes to a deterministic endpoint: the dead letter office.
Forwarding
Messageforwardingis another method of sending a message to an actor. It works such that the one doing the forwarding is invisible to the receiver. If A sends toBandB forwards that message toC, thenC sees the sender of the message as A, notB. It should now be fairly obvious how forwarding works. When an actor forwards a message from itself to anotherActorRef, it’s really thesenderthat’s most important. For example, the equivalent of a call toforwardis most definitely not:
case msg @ SomeMessage =>
someOtherActor ! msg
When the receiving actor,someOtherActor, accesses the sender in order to know who to respond to, the value he’ll get is the one that made the call to !, not the original sender, and that’s not the semantic of forwarding a message. When you forward a message, you’re handing it off to someone else, and it’s supposed to look like you were never involved. This is the same as forwarding a phone call. The caller is passed off to someone else, and the guy who did the passing isn’t involved in the conversation anymore.
Therefore, thatforwardonly needs to preserve the original sender in order to make the forward a success.
def forward (message: Any)(implicit context: ActorContext): Unit
The implicit parameter here is a bit confusing. You would expect that theimplicitwould be anActorRef, but the implicitActorRefin the actor is already known to beself, which won’t work here since it’s the reference toselfthat we’re trying to avoid. It turns out that theActorContextholds enough information that we can extract the original sender and thus have a nice interface (i.e., theimplicitparameter) and deterministic behaviour.
TheActorContextwill be covered in more detail in later chapters.
We aren’t giving too much away about Akka internals if we show the implementation of forward because it’s only doing what we would guess that it’s doing anyway:
def forward(message: Any)(implicit context: ActorContext) = tell(message, context.sender)
These different ways of sending are summarized inFigure 5.5.
Receiving Actor Sending
Actor
Message
Hitchhiker ActorRef
Intermediary Actor Sending
Actor
Message
Hitchhiker ActorRef
Intermediary Actor Message
Hitchhiker ActorRef Normal Message Send
Message Forwarding
Receiving Actor Message
Hitchhiker ActorRef Sending without a Sender Sending
Function
Dead Letters Actor
Figure 5.5ãThe different ways of sending messages to actors.