Akka's documentation says:
def same[T]: Behavior[T]Return this behavior from message processing in order to advise the system to reuse the previous behavior. This is provided in order to avoid the allocation overhead of recreating the current behavior where that is not necessary.
def setup[T](factory: (ActorContext[T]) => Behavior[T]): Behavior[T]
setupis a factory for a behavior. Creation of the behavior instance is deferred until the actor is started, as opposed to Behaviors.receive that creates the behavior instance immediately before the actor is running. The factory function pass theActorContextas parameter and that can for example be used for spawning child actors.
setupis typically used as the outer most behavior when spawning an actor, but it can also be returned as the next behavior when processing a message or signal. In that case it will be started immediately after it is returned, i.e. next message will be processed by the started behavior.
I have tried to construct an actor system with the following guardian behavior:
public static Behavior<SayHello> create() {
return Behaviors.setup(ctx -> {
System.out.println(ctx.getSelf().path() + ": returning same()");
return Behaviors.same();
});
}
I was expecting Akka to recursively apply the behavior in question. In other words, I was expecting Akka to produce this infinite output:
akka://helloakka/user: returning same
akka://helloakka/user: returning same
akka://helloakka/user: returning same
...
However, it just prints it one time.
Is this behavior expected? What is the actual meaning of the behavior I provided? Can you devise a scenario where returning same from within setup makes sense?
Edit: I did another experiment, where I return the named behavior itself instead of same. I expected no differences, since that same should just be an optimization for reusing the previous behavior rather than allocating a new one. However, to my surprise, the output is actually infinite.
public static Behavior<SayHello> create() {
return Behaviors.setup(ctx -> {
System.out.println(ctx.getSelf().path() + ": returning create()");
return create();
});
}
What am I missing here?
Behaviors.setup(in the recent implementations: this hasn't changed semantically since prior to 2.6) is just a factory for anakka.actor.typed.internal.BehaviorImpl.DeferredBehavior(since your examples are using the javadsl, I'm starting from the javadsl; the scaladsl is the same under the hood here):Where
DeferredBehavioris (omitting things liketoStrings):Note that the
factoryisn't called untilDeferredBehavior::applyis called.When you spawn an actor with that behavior (
DeferredBehavior), a classic actor which is an instance ofActorAdapteris spawned.Behavior.startis effectively, for our purposes:So now we call the
factorymethod, which in this case ultimately returnsBehaviorImpl.SameBehaviorafter executing yourprintln. ThisSameBehaviorgets passed tovalidateAsInitial, which throws anIllegalArgumentExceptionbecauseBehaviors.sameandBehaviors.unhandledaren't valid initial behaviors. This exception effectively kills the actor as it's being born (grisly, I know).When you call back into
create, on the other hand, thefactorywill return anotherDeferredBehaviorwith the samefactory, so that will get repeatedly passed tostart; depending on whether the Scala compiler used to build Akka noticed thatBehavior.startin this case is tail-recursive this would either result in an infinite loop or a stack overflow.A
Behaviors.setupwhich results in aBehaviors.sameonly makes sense if you want an actor to be stillborn. The side effects inBehaviors.setupwill still happen, but if that's all you want, why not just do them directly and save the pointless overhead?The foregoing technically only applies to normal actors. The guardian behavior is special, in that it first waits for the delivery of a special message from the actor system signalling that the actor system is ready, after which it wraps the behavior in an interceptor which tears down the actor system if the behavior is no longer alive (viz. the behavior is stopped or failed). At no point is the behavior validated as initial, but the wrapped behavior is
started as above, which runs theBehaviors.setupblock once and forgets thefactory.At this point the behavior is a bare
SameBehaviorwhich hasn't handled a single message. If you sent a message to theActorSystem(which is anActorRef), it would be interpreted byBehaviors.interpretwhich would find theSameBehaviorand throw then, which would be a crash of the user actor hierarchy, but doesn't seem to (in my experiment) stop the actor system.