So I was playing with different behaviors in Akka. When I executed this code:
@Override
public Receive<CommonCommand> createReceive() {
return notYetStarted();
}
public Receive<CommonCommand> notYetStarted() {
return newReceiveBuilder()
.onMessage(RaceLengthCommand.class, message -> {
// business logic
return running();
})
.build();
}
public Receive<CommonCommand> running() {
return newReceiveBuilder()
.onMessage(AskPosition.class, message -> {
if ("some_condition") {
// business logic
return this;
} else {
// business logic
return completed(completedTime);
}
})
.build();
}
public Receive<CommonCommand> completed(long completedTime) {
return newReceiveBuilder()
.onMessage(AskPosition.class, message -> {
// business logic
return this;
})
.build();
}
I got following log:
21:46:41.038 [monitor-akka.actor.default-dispatcher-6] INFO akka.actor.LocalActorRef - Message [learn.tutorial._5_racing_game_akka.RacerBehavior$AskPosition] to Actor[akka://monitor/user/racer_1#-1301834398] was unhandled. [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'.
Initially the RaceLengthCommand
message is sent to notYetStarted()
behavior. That works fine. Then this behavior should transition to running()
behavior, and this second one should receive the message AskPosition
.
But according to my tests, the AskPosition
message is delivered to notYetStarted()
behavior. This contradicts my whole understanding of the concept.
I confirmed this by copying the onMessage()
part from running()
behavior and pasting on notYetStarted()
behavior. Now the code executes fine and no more deadletters.
So apparently notYetStarted()
behavior is indeed receiving messages even after I switched behaviors? Why is this happening???
It seems like your actor definition is mixing the OO and functional styles and hitting some warts in the interplay between using those styles in the same actor. Some of this confusion arises from Receive
in the Java API being a Behavior
but not an AbstractBehavior
. This may be a vestige of an earlier evolution of the API (I've suggested to the Akka maintainers that this vestige be dropped in 2.7 (which is the absolute earliest it could be dropped owing to binary compatibility); communications with some of them haven't yielded a reason for this distinction and there's no such distinction in the analogous Scala API).
Disclaimer: I tend to work exclusively in the Scala functional API for actor definition.
There's a duality in the actor model between the state of an actor (i.e. its fields) and its behavior (how it responds to the next message it receives): to the world outside the actor, they are one and the same because the only way (ignoring something like taking a heap dump) to observe the actor's state is to observe its response to a message. Of course, in fact, the current behavior is a field in the runtime representation of the actor, and in the functional definitions, the behavior generally has fields for state.
The OO style of behavior definition favors:
The functional style of behavior definition favors:
(the distinction is analogous to imperative programming having a while/for loop where a variable is updated vs. functional programming's preference for defining a recursive function which the compiler turns behind the scenes into a loop).
The AbstractBehavior
API seems to assume that the message handler is createReceive()
: returning this
within an AbstractBehavior
means to go back to the createReceive()
. Conversely, Behaviors.same()
in the functional style means "whatever the current behavior is, don't change it". Where there are multiple sub-Behavior
s/Receive
s in one AbstractBehavior
, this difference is important (it's not important when there's one Receive
in the AbstractBehavior
).
TL;DR: if defining multiple message handlers in an AbstractBehavior
, prefer return Behaviors.same
to return this
in message handlers. Alternatively: only define one message handler per AbstractBehavior
.