简体   繁体   中英

Akka: lost reference for `child.path.name` on ask

I am trying to implement a Java version of the "Up and Running" example from Manning's "Akka in Action" book. It is a simple Http server based on Actor Model for saving (just in memory) and retrieving some events. I have no problem saving the events. But I do have an issue when querying my actor system for events (all events).

This is the relevant (I've put triple dots instead of code that I think has nothing to do with my issue) code for BoxOffice - parent actor for all TicketSeller s (later are responsible for managing state for each event).

public class BoxOffice extends AbstractActor {

    ...
    private Timeout timeout;
    final static String NAME = "boxOffice";

    //create child actors
    private ActorRef createTicketSeller(String name) {
        return getContext().actorOf(TicketSeller.props(name));
    }

    public BoxOffice(Timeout timeout) {
        this.timeout = timeout;
    }

    //the only method of an actor
    @Override
    public Receive createReceive() {
        return receiveBuilder()
                ...
                ...
                .match(GetEvent.class, this::receiveMsgGetEvent)
                .match(GetEvents.class, this::receiveMsgGetEvents)
                ...
                .build();
    }

    ...

    private void receiveMsgGetEvent(GetEvent getEvent) {
        Optional<ActorRef> maybeChild = getChildByName(getEvent.getName());
        log.info(String.format("Asking for event %s. Child is present: %s", getEvent.getName(), maybeChild.isPresent()));
        OptionalConsumer.of(maybeChild)
                .ifPresent(child -> child.forward(new TicketSeller.GetEvent(), getContext()))
                .ifNotPresent(() -> getSender().tell(Optional.empty(), getSelf()));
    }

    private void receiveMsgGetEvents(GetEvents getEvents) {
        //ask self() for each of the passed-in event
        List<CompletableFuture<Optional<Event>>> listFutureMaybeEvent =
                allChildrenStream()
                .map(child ->
                        ask(getSelf(), new GetEvent(child.path().name()), timeout)
                        .thenApply(obj -> (Optional<Event>) obj)
                        .toCompletableFuture())
                .collect(toList());

        CompletableFuture<Events> eventsFuture = toFutureEvents(listFutureMaybeEvent);
        pipe(eventsFuture, getContext().dispatcher()).to(sender());
    }

    private Stream<ActorRef> allChildrenStream() {
        return StreamSupport.stream(getContext().getChildren().spliterator(), false);
    }

    ...

    private CompletableFuture<Events> toFutureEvents(List<CompletableFuture<Optional<Event>>> futurePossibleEvents) {
        List<Event> events = futurePossibleEvents.stream()
                .map(CompletableFuture::join)
                .filter(Optional::isPresent)
                .map(Optional::get)
                .collect(toList());
        return CompletableFuture.supplyAsync(() -> new Events(events));
    }

    ...

    private Optional<ActorRef> getChildByName(String name) {
        return getContext().findChild(name);
    }

    static Props props(Timeout timeout) {
        return Props.create(BoxOffice.class, () -> new BoxOffice(timeout));
    }

Basically what happens is that in receiveMsgGetEvents I am sending a message to self with a message containing a child name child.path.name . However when I receive that message (in, respectively, receiveMsgGetEvent ), child actor cannot be found by that name:

INFO  [BoxOffice]: Asking for event $a. Child is present: false

Also it is noteworthy that it takes quite long between GetEvent is sent and is received by the same actor(like seconds, but my feeling it is less then 20).

Issue could be due to my CompletableFutures manipulations, but I've tried to reproduce scala equivalent code.

The info log from above along with this message:

INFO  [DeadLetterActorRef]: Message [java.util.Optional] from Actor[akka://mycompanyAkkaDemo/user/boxOffice#1554115585] to Actor[akka://mycompanyAkkaDemo/deadLetters] was not delivered. [1] dead letters encountered. This logging...

are printed after the stacktrace which is printed after configured timeout (20 seconds):

ERROR [ActorSystemImpl]: Error during processing of request: 'Ask timed out on [Actor[akka://mycompanyAkkaDemo/user/boxOffice#1554115585]] after [20000 ms]. Sender[null] sent message of type "com.mycompany.demo.messages.boxoffice.GetEvents".'. Completing with 500 Internal Server Error response. To change default exception handling behavior, provide a custom ExceptionHandler.
akka.pattern.AskTimeoutException: Ask timed out on [Actor[akka://mycompanyAkkaDemo/user/boxOffice#1554115585]] after [20000 ms]. Sender[null] sent message of type "com.mycompany.demo.messages.boxoffice.GetEvents".
    at akka.pattern.PromiseActorRef$.$anonfun$defaultOnTimeout$1(AskSupport.scala:595)
    at akka.pattern.PromiseActorRef$.$anonfun$apply$1(AskSupport.scala:605)
    at akka.actor.Scheduler$$anon$4.run(Scheduler.scala:140)
    ...
    at java.lang.Thread.run(Thread.java:748)
ERROR [OneForOneStrategy]: akka.pattern.AskTimeoutException: Ask timed out on [Actor[akka://mycompanyAkkaDemo/user/boxOffice#1554115585]] after [20000 ms]. Sender[null] sent message of type "com.mycompany.demo.messages.boxoffice.GetEvent".
java.util.concurrent.CompletionException: akka.pattern.AskTimeoutException: Ask timed out on [Actor[akka://mycompanyAkkaDemo/user/boxOffice#1554115585]] after [20000 ms]. Sender[null] sent message of type "com.mycompany.demo.messages.boxoffice.GetEvent".
    at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:292)
    at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:308)
    at java.util.concurrent.CompletableFuture.uniApply(CompletableFuture.java:593)
    ...
Caused by: akka.pattern.AskTimeoutException: Ask timed out on [Actor[akka://mycompanyAkkaDemo/user/boxOffice#1554115585]] after [20000 ms]. Sender[null] sent message of type "com.mycompany.demo.messages.boxoffice.GetEvent".
    at akka.pattern.PromiseActorRef$.$anonfun$defaultOnTimeout$1(AskSupport.scala:595)
    ... 11 common frames omitted

What is going wrong here is that there is blocking on the dispatcher.

On the JVM, threads backed by operating system threads, which are expensive both in memory and process scheduler overhead. One of the advantages of Akka is that it allows more efficient use of threads, by allowing you to run many actors on a smaller number of threads.

This is great, but does mean you should never perform a blocking call inside an actor. The CompletableFuture::join call here is blocking, which likely is the cause of your problem.

By avoiding blocking calls and using async API's (such as CompletableFuture.allOf ) your problem should go away.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM