简体   繁体   中英

Running a CompletableFuture from an Actor

I'm playing around with reactive patterns in a Java (8) Spring Boot (1.5.2.RELEASE) application with Akka (2.5.1). It's coming along nicely but now I'm stuck trying to run a CompletableFuture from an actor. To simulate this I have created a very simple service that returns a CompletableFuture. However, when I then try to return the result to the calling controller I get errors about dead-letters and no response is returned.

The error I am getting is:

[INFO] [05/05/2017 13:12:25.650] [akka-spring-demo-akka.actor.default-dispatcher-5] [akka://akka-spring-demo/deadLetters] Message [java.lang.String] from Actor[akka://akka-spring-demo/user/$a#-1561144664] to Actor[akka://akka-spring-demo/deadLetters] was not delivered. [1] dead letters encountered. This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'.

Here is my code. This is the controller calling the actor:

@Component
@Produces(MediaType.TEXT_PLAIN)
@Path("/")
public class AsyncController {
    @Autowired
    private ActorSystem system;

    private ActorRef getGreetingActorRef() {
        ActorRef greeter = system.actorOf(SPRING_EXTENSION_PROVIDER.get(system)
                  .props("greetingActor"));

        return greeter;
    }

    @GET
    @Path("/foo")
    public void test(@Suspended AsyncResponse asyncResponse, @QueryParam("echo") String echo) {
        ask(getGreetingActorRef(), new Greet(echo), 1000)
            .thenApply((greet) -> asyncResponse.resume(Response.ok(greet).build()));
    }
}

Here is the service:

@Component
public class GreetingService {
    public CompletableFuture<String> greetAsync(String name) {
        return CompletableFuture.supplyAsync(() -> "Hello, " + name);
    }
}

Then here is the actor receiving the call. At first I had this:

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class GreetingActor extends AbstractActor {
    @Autowired
    private GreetingService greetingService;

    @Autowired
    private ActorSystem system;

    @Override
    public Receive createReceive() {
        return receiveBuilder()
                .match(Greet.class, this::onGreet)
                .build();
    }

    private void onGreet(Greet greet) {
        greetingService.greetAsync(greet.getMessage())
            .thenAccept((greetingResponse) -> getSender().tell(greetingResponse, getSelf()));
    }

}

This resulted in 2 calls being handled correctly but after that I would get dead-letter errors. Then I read here what was probably causing my problems: http://doc.akka.io/docs/akka/2.5.1/java/actors.html

Warning When using future callbacks, inside actors you need to carefully avoid closing over the containing actor's reference, ie do not call methods or access mutable state on the enclosing actor from within the callback. This would break the actor encapsulation and may introduce synchronization bugs and race conditions because the callback will be scheduled concurrently to the enclosing actor. Unfortunately there is not yet a way to detect these illegal accesses at compile time. See also: Actors and shared mutable state

So I figured the idea is that you pipe the result to self() after which you can do getSender().tell(response, getSelf()).

So I altered my code to this:

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class GreetingActor extends AbstractActor {
    @Autowired
    private GreetingService greetingService;

    @Autowired
    private ActorSystem system;

    @Override
    public Receive createReceive() {
        return receiveBuilder()
                .match(Greet.class, this::onGreet)
                .match(String.class, this::onGreetingCompleted)
                .build();
    }

    private void onGreet(Greet greet) {
        pipe(greetingService.greetAsync(greet.getMessage()), system.dispatcher()).to(getSelf());
    }

    private void onGreetingCompleted(String greetingResponse) {
        getSender().tell(greetingResponse, getSelf());
    }

}

The onGreetingCompleted method is being called with the response from the GreetingService but at that time I again get the dead-letters error so for some reason it can't send the response back to the calling controller.

Note that if I change the service to this:

@Component
public class GreetingService {
    public String greet(String name) {
        return "Hello, " + name;
    }
}

And the onGreet in the actor to:

private void onGreet(Greet greet) {
    getSender().tell(greetingService.greet(greet.getMessage()), getSelf());
}

Then everything works fine. So it would appear that I have my basic Java/Spring/Akka set up correctly, it's just when trying to call a CompletableFuture from my actor that the problems start.

Any help would be much appreciated, thanks!

The getSender method is only reliably returning the ref of the sender during the synchronous processing of the message .

In your first case, you have:

 greetingService.greetAsync(greet.getMessage())
        .thenAccept((greetingResponse) -> getSender().tell(greetingResponse, getSelf()));

Which means that getSender() is invoked async once the future completes. Not reliable anymore. You can change that to:

 ActorRef sender = getSender();
 greetingService.greetAsync(greet.getMessage())
        .thenAccept((greetingResponse) -> sender.tell(greetingResponse, getSelf()));

In your second example, you have

pipe(greetingService.greetAsync(greet.getMessage()), system.dispatcher()).to(getSelf());

You are piping the response to "getSelf()", ie your worker actor. The original sender will never get anything (thus the ask expires). You can fix that into:

pipe(greetingService.greetAsync(greet.getMessage()), system.dispatcher()).to(getSender());

In the third case, you have getSender() being executed synchronously during the processing of the message, thus it works.

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