Capability of creating children to an arbitrary actor from anywhere in an application without messaging it

Hi,
I have received excellent answers in my previous post, thus I’d like to take the opportunity to make some more questions.
I am making these questions in the context of my master thesis, for which I desire to understand Akka’s design. In particular, I always wonder “Why does Akka do things this way? Is there a philosophical or technical reason?”.

I am reading Learning Akka Typed from Classic:

akka.actor.ActorSystem has its correspondence in akka.actor.typed.ActorSystem. One difference is that when creating an ActorSystem in Typed you give it a Behavior that will be used as the top level actor, also known as the user guardian.
Additional actors for an application are created from the user guardian alongside performing the initialization of Akka components such as Cluster Sharding. In contrast, in a classic ActorSystem, such initialization is typically performed from the “outside”.
The actorOf method of the classic ActorSystem is typically used to create a few (or many) top level actors. The ActorSystem in Typed doesn’t have that capability. Instead, such actors are started as children of the user guardian actor or children of other actors in the actor hierarchy.

So far, I understand. My questions come from the remaining of the paragraph:

The rationale for this is partly about consistency. In a typed system you can’t create children to an arbitrary actor from anywhere in your app without messaging it, so this will also hold true for the user guardian actor. That noted, in cases where you do need to spawn outside of this guardian then you can use the SpawnProtocol to spawn as needed.

Where does this limitation come from?

My guess is that, in Akka Classic, we have a full Actor object exposing an ActorContext: whoever has such ActorContext can arbitrary create children to such actor.
In Akka Typed, instead, we only ever have a typed ActorRef of an actor, so we lose the capability in question.

I am wondering what are the reasons for Akka Typed for not returning a typed ActorRef together with a typed ActorContext, or similar. This would overcome the limitation in question.

Perhaps there is a “philosophical” argument for this, constituted by the design principle that the only way of changing an actor’s state must happen through means of message passing (thus, including adding a child to it).

Thanks to anyone who will answer.

As with the parent-only stopping of actors, parent-only creation has both philosophical and technical reasons which are even more clearly interlinked in this case. Since the parent is responsible for the whole lifecycle of the child, it needs to know all its children. And since actor creation is an asynchronous process, this extends to knowing of the intent of creation already because otherwise a parent may be surprised by a failure during child creation.

This immediately translates into the technical domain, where we could have chosen to “solve” this dilemma by making the parent’s private state accessible from the outside (which requires locking, something Akka doesn’t do at all). Such an approach would counteract all work of isolating an actor from the rest of the system to gain the freedom of using internal state without concurrency concerns, so it complicates both the mental model (philosophy) and the technical implementation (locking).

As shown in the docs you link to, Behaviors.setup(ctx => ...), a typed actor does have access to an ActorContext, not just an ActorRef, which models the above philosophical restriction in terms of programming language types — the only thing missing from JVM languages is the ability of preventing you from sending an ActorContext to some other place and wreaking havoc from outside your actor.


Another aspect you touch upon is the separation between ActorRef and ActorContext: these correspond to the sending and receiving ends of the mailbox (or “channel” in other theoretical frameworks). Encapsulation demands that we expose only the sender to the outside and keep the receiver on the inside.

In CSP or π-calculus you can send around receivers, but this makes implementations of these theories non-distributed, only concurrent (since non-local receive requires consensus between nodes, becomes unavailable during network partitions, etc.). There are variants like localized π-calculus that address this point — and look a lot like actors :slight_smile:

3 Likes

Thank you so much for dedicating time to answer my questions. This kind of information is really useful in what I am working on, which I hope I will be able to show you when it will reach a consistent state.

You’re very welcome!

It’s also worth noting that while the ActorContext can be passed outside the actor, most of the methods in it (basically everything but getting the self ActorRef, the actor system, and maybe a couple of others) now validate that they’re being called by the thread running their actor’s behavior.

1 Like

I suppose that these kinds of runtime checks are also in place because Scala does not have a way to express ownership, such as Rust

Hi @andreastedile

Maybe my answer is not fully relevant to your question but still want to highlight another way of creating actors from “outside”. The implementation looks pretty similar to the SpawnProtocol which is provided by the Akka but instead, any actor in the actor system’s hierarchy can be a spawner.
Of course, to access the actor from outside you need to communicate with the actor using message(s).

I prepared a small library as an example which worked fine for my use cases at work, have a look if you are interested ;)
The link: GitHub - dmmax/akka-spawn-protocol: Uses Spawn Protocol to create Akka Actors from outside. It can be applied at any level of actors' hierarchy

1 Like