Bart Kiers
Bart Kiers

Reputation: 170158

Creating an ActorSelection to get an ActorRef created in parent actor

I have an Akka system with a ParentOrderActor that receives Create and Read messages to create- and read child-OrderActor respectively.

All goes well if I create all Actors in the main "/user/..." path:

// Inside the ParentOrderActor ("/user/orders")

// Create in "/user"
getContext().system().actorOf(new Props(OrderActor.class), "ABC");

// Read via an ActorSelection
ActorSelection actorSelection = getContext().system().actorSelection("/user/ABC");

The logs show this when all goes well:

ParentOrderActor -> created: Actor[akka://system/user/ABC#1132819541]
ParentOrderActor -> received: Message[name=Read, id=ABC]
ParentOrderActor -> actorSelection: ActorSelection[Actor[akka://system/]/user/ABC]
ParentOrderActor -> for id=Message[name=Read, id=ABC], we retrieved ActorRef: Actor[akka://system/user/ABC#1132819541]

But when trying to create- and read the child actors inside the ParentOrderActor:

// Inside the ParentOrderActor ("/user/orders")

// Create in "/user/orders"
getContext().actorOf(new Props(OrderActor.class), "ABC");

// Read via an ActorSelection
ActorSelection actorSelection = getContext().actorSelection("ABC");

this is being printed:

ParentOrderActor -> created: Actor[akka://system/user/orders/$a/ABC#-436492577]
ParentOrderActor -> received: Message[name=Read, id=ABC]
ParentOrderActor -> actorSelection: ActorSelection[Actor[akka://system/user/orders/$a#478613574]/ABC]
Oops: java.util.concurrent.TimeoutException: Futures timed out after [2 seconds]
ParentOrderActor -> for id=Message[name=Read, id=ABC], we retrieved ActorRef: null

I've tried all kind of paths in the actorSelection(...), but the ActorRef is always null.

For completeness sake, here is a short SSCCE that demonstrates the above:

public class Main {

    private static boolean createChildrenInUser = false;
    private static LoggingAdapter log;

    static abstract class Message {

        final String id;

        Message(String id) {
            this.id = id;
        }

        @Override
        public String toString() {
            return "Message[name=" + getClass().getSimpleName() + ", id=" + id + "]";
        }
    }

    static class Create extends Message {
        Create(String id) {
            super(id);
        }
    }

    static class Read extends Message {
        Read(String id) {
            super(id);
        }
    }

    static class ParentOrderActor extends UntypedActor {

        @Override
        public void preStart() {
            log.debug("ParentOrderActor -> starting...");
        }

        @Override
        public void onReceive(Object message) throws Exception {

            log.debug("ParentOrderActor -> received: {}", message);

            if(message instanceof Create) {
                if(createChildrenInUser) {
                    // Create an Actor in the root context ("/user/id").
                    ActorRef created = getContext().system().actorOf(new Props(OrderActor.class), ((Create)message).id);
                    log.debug("ParentOrderActor -> created: {}", created);
                }
                else {
                    // Create an Actor in this ParentOrderActor's context ("/user/orders/id")
                    ActorRef created = getContext().actorOf(new Props(OrderActor.class), ((Create)message).id);
                    log.debug("ParentOrderActor -> created: {}", created);
                }
            }
            else if(message instanceof Read) {
                ActorRef ref = select((Message)message);
                log.debug("ParentOrderActor -> for id={}, we retrieved ActorRef: {}", message, ref);
                getContext().system().shutdown();
            }
            else {
                unhandled(message);
            }
        }

        private ActorRef select(Message message) {
            try {
                ActorSelection actorSelection;
                if(createChildrenInUser) {
                    // Select the Actor with a certain id in the root context ("/user/id").
                    actorSelection = getContext().system().actorSelection("/user/" + message.id);
                }
                else {
                    // Create an Actor in this ParentOrderActor's context ("/user/orders/id")
                    actorSelection = getContext().actorSelection(message.id);
                }
                log.debug("ParentOrderActor -> actorSelection: {}", actorSelection);
                Timeout timeout = new Timeout(2, TimeUnit.SECONDS);
                AskableActorSelection askableActorSelection = new AskableActorSelection(actorSelection);
                Future<Object> future = askableActorSelection.ask(new Identify(1), timeout);
                ActorIdentity actorIdentity = (ActorIdentity) Await.result(future, timeout.duration());
                return actorIdentity.getRef();
            }
            catch(Exception e) {
                log.debug("Oops: {}", e);
                return null;
            }
        }
    }

    static class OrderActor extends UntypedActor {

        @Override
        public void preStart() {
            log.debug("OrderActor -> starting...");
        }

        @Override
        public void onReceive(Object message) throws Exception {
            log.debug("OrderActor -> received: {}", message);
            unhandled(message);
        }
    }

    public static void main(String[] args) {

        ActorSystem system = ActorSystem.create("system");

        log = Logging.getLogger(system, Main.class);

        ActorRef orders = system.actorOf(new Props(ParentOrderActor.class).withRouter(new RoundRobinRouter(1)), "orders");

        orders.tell(new Create("ABC"), orders);

        orders.tell(new Read("ABC"), orders);
    }
}

Running the code above will cause the Read to return an ActorRef which is null. Changing the boolean flag createChildrenInUser to true will create the child actors inside "/user" and all goes well.

The question: how do I create an ActorSelection and get an ActorRef of an actor that is created inside /user/orders (ParentOrderActor)?

Upvotes: 2

Views: 4526

Answers (1)

cmbaxter
cmbaxter

Reputation: 35443

If I understand your problem correctly and you want the best way to load a child actor from within the parent actor, then you can use:

getContext().child("ABC")

That will return a scala.Option<ActorRef> though and not an ActorSelection, so if you wanted to lookup multiple children with wildcarding and send them all a message, this approach will not work.

Upvotes: 5

Related Questions