How to find all children of an actor?
This topic is based on a conversation between an anonymous user of Akka and our “Ask AI about Akka” bot in the documentation.
How to find all children of an actor?
This topic is based on a conversation between an anonymous user of Akka and our “Ask AI about Akka” bot in the documentation.
In Akka, you can retrieve the ActorRef
of started child actors using the children
method of the ActorContext
. However, the type of the returned ActorRef
is unknown, since different types can be used for different children. Therefore, this is not a useful way to lookup children when the purpose is to send messages to them.
Instead of finding children via the ActorContext
, it is recommended to use an application specific collection for bookkeeping of children, such as a Map[String, ActorRef[Child.Command]]
. Here is an example in Scala:
object Parent {
sealed trait Command
case class DelegateToChild(name: String, message: Child.Command) extends Command
private case class ChildTerminated(name: String) extends Command
def apply(): Behavior[Command] = {
def updated(children: Map[String, ActorRef[Child.Command]]): Behavior[Command] = {
Behaviors.receive { (context, command) =>
command match {
case DelegateToChild(name, childCommand) =>
children.get(name) match {
case Some(ref) =>
ref ! childCommand
Behaviors.same
case None =>
val ref = context.spawn(Child(), name)
context.watchWith(ref, ChildTerminated(name))
ref ! childCommand
updated(children + (name -> ref))
}
case ChildTerminated(name) =>
updated(children - name)
}
}
}
updated(Map.empty)
}
}
And here is an example in Java:
public class Parent extends AbstractBehavior<Parent.Command> {
public interface Command {}
public static class DelegateToChild implements Command {
public final String name;
public final Child.Command message;
public DelegateToChild(String name, Child.Command message) {
this.name = name;
this.message = message;
}
}
// ... rest of the code ...
private Behavior<Command> onDelegateToChild(DelegateToChild command) {
ActorRef<Child.Command> ref = children.get(command.name);
if (ref == null) {
ref = getContext().spawn(Child.create(), command.name);
getContext().watchWith(ref, new ChildTerminated(command.name));
children.put(command.name, ref);
}
ref.tell(command.message);
return this;
}
private Behavior<Command> onChildTerminated(ChildTerminated command) {
children.remove(command.name);
return this;
}
}
Remember to remove entries from the Map
when the children are terminated. For that purpose it’s convenient to use watchWith
, as illustrated in the example above, because then you can include the key to the Map in the termination message. In that way the name of the actor doesn’t have to be the same as identifier used for bookkeeping.
Retrieving the children from the ActorContext can still be useful for some use cases, such as:
unsafeUpcast
of the ActorRef
is considered “safe enough”Sources:
How can I issue a PoisonPill event to all the children of an actor?
In Akka, you can send a PoisonPill
message to an actor to stop it. If you want to stop all children of an actor, you can iterate over the children and send a PoisonPill
to each one. However, please note that PoisonPill
is not supported in Akka Typed. Instead, you should define a message that the actor understands and let it return Behaviors.stopped
when receiving that message.
Here is an example of how you might do this in Akka Classic:
Scala:
context.children.foreach(_ ! PoisonPill)
Java:
for (ActorRef child : getContext().getChildren()) {
child.tell(PoisonPill.getInstance(), getSelf());
}
If you are using Akka Typed, you would need to define a custom stop message:
Scala:
sealed trait Command
case object Stop extends Command
def apply(): Behavior[Command] = Behaviors.receive { (context, message) =>
message match {
case Stop => Behaviors.stopped
}
}
// To stop all children
context.children.foreach(_ ! Stop)
Java:
public interface Command {}
public enum Stop implements Command { INSTANCE }
public Behavior<Command> create() {
return Behaviors.receive(Command.class)
.onMessage(Stop.class, msg -> Behaviors.stopped())
.build();
}
// To stop all children
for (ActorRef<Command> child : getContext().getChildren()) {
child.tell(Stop.INSTANCE);
}
Please note that the PoisonPill
message will be enqueued as ordinary messages and will be handled after messages that were already queued in the mailbox. This means that the actor will continue processing its current messages before it stops.
Sources: