The akka documentation is clearly stated that it is dangerous to create an actor within an actor like this:
class ActorA extends Actor { def receive = ??? } final class ActorB extends Actor { def receive = { case _ => val act = context.actorOf(Props(new ActorA)) }}
I understand that the Actor's apply method is accepting this
reference of the creating actor. yet I couldn't understand (nor couldn't find any example) why this is harmful and what issues it can cause?
3 Answers
Answers 1
Let's tweak your example a little bit
class ActorA(str:String) extends Actor { def receive = ??? } final class ActorB extends Actor { def receive = { case _ => val act = context.actorOf(Props(new ActorA("hidden"))) }}
Most of the common use case of using actors are to handle failover and supervision, shen an actor fails and needs to be restarted, the actor system needs to know how to do that. When you use Props(Props(new ActorA)), you've hidden the parameter value of "hidden" by handling it yourself.
Rather than doing that if instead, you declare how to create instances of the actor, the actor system will know exactly what it needs to do when recreating an actor - i.e. create an instance of ActorA with a constructor argument of "hidden".
Even with your example of Actor without param
context.actorOf(Props(new ActorA))
this way of instantiating actors within another actor is not recommended because it encourages to close over the enclosing scope, resulting in non-serializable Props and possibly race conditions (breaking the actor encapsulation).
Answers 2
I believe we are confusing creation and declaration. The doc says that
Declaring one actor within another is very dangerous and breaks actor encapsulation. Never pass an actor’s this reference into Props!
So the problem is declaration, not creation! Let's look at Java's:
public class MyActor extends AbstractActor { @Override public Receive createReceive() { return ReceiveBuilder.create() .match(String.class, handleString()) .matchAny(x -> unhandled(x)) .build(); } private FI.UnitApply<String> handleString() { return message -> sender().tell("OK", getSelf()); } class MyOtherActor extends AbstractActor { @Override public Receive createReceive() { return ReceiveBuilder.create() .match(String.class, handleString()) .matchAny(x -> unhandled(x)) .build(); } private FI.UnitApply<String> handleString() { return message -> sender().tell("OK-Inner", getSelf()); } } }
Now, if MyOtherActor was a normal class, we'd be able to instantiate it only from an instance of MyActor:
MyActor actor = new MyActor(); MyActor.MyOtherActor otherActor = actor.new MyOtherActor();
Which means that the constructor for MyOtherActor depends on the instance of MyActor!
Now, if Props are supposed to contain the "factory" of the actor. They need a factory method. If our MyOtherActor is declared as we did here, then our props would look like this (ish):
MyActor actor = ??? // how did you even get a reference to the actor and not the actorRef in the first place! Props otherActorProps = Props.create(MyActor.MyOtherActor.class, () -> actor.new MyOtherActor());
And bang, here comes the kicker! Now your otherActorProps
contains a reference to actor
, i.e. you have closed over mutable state! If for whatever reason actor
"dies", your props will still be referencing it, causing all sort of weirdness.
There is also the issue of how you get a reference to the actor
in the first place, and not it's actorRef
IMHO, that's what the documentation is referring to, and not the fact of "creating" (i.e. instantiating, spawning) an actor within another one: that's absolutely normal and it's a routine operation of akka (that's why you can do getContext().actorOf(..)
as well as actorSystem.actorOf(...)
Answers 3
The warning is there in the documentation because it's easy to accidentally close over the creating actor's state, including its this
pointer (which you should never use in actor-based code).
In my experience, I've usually seen a props
method put into an actor's companion object:
object ActorA { def props() = Props(new ActorA) }
Doing it that way ensures the returned Props
isn't closing over an actor's state.
class ActorB extends Actor { def receive = { case _ => val actorB = context.actorOf(ActorA.props) ... } }
It's not as big of a possibility for actors that don't take constructor parameters, but once parameters come into play you need to be careful about closing over internal state.
0 comments:
Post a Comment