Is it possible to use Akka Patterns.ask() where a classic actor asks a typed actor?

62 Views Asked by At

Using Akka (Java) I am attempting to use the ask pattern (akka.pattern.Patterns.ask) where the asker is a classic actor and the askee is a typed actor. I am attempting something like this in the classic actor

Patterns.ask(Adapter.toClassic(typedActor), new TypedActor.Message(Adapter.toTyped(self())), timeout));

In the typed actor, the message is seen and replied to (e.g., like I show below), but the ask times out

msg.replyTo.tell(new Response());

Is this possible, and if so, could you help me with a simple example for how to do this in Java?

2

There are 2 best solutions below

2
Levi Ramsey On BEST ANSWER

It looks like you're using Akka Classic's implementation of the ask pattern, akka.pattern.Patterns.ask. This implementation is logically equivalent (there are some slight optimizations under the hood) to spawning an actor which schedules a timeout message to itself and then sending a message to the askee purporting to be from the spawned actor. The spawned actor completes the future successfully if it gets a response before the timeout message or fails it with a timeout otherwise.

In

Patterns.ask(Adapter.toClassic(typedActor), new TypedActor.Message(Adapter.toTyped(self())), timeout));

you're setting the classic actor which initiated the ask as the replyTo on the message, so the response from the typed actor goes to that classic actor, not to the actor which ask spawned, so that spawned actor will report that the ask timed out. The typed behavior doesn't see the "return address" on the classic envelope.

The easiest fix is probably to use the Akka Typed ask pattern, akka.actor.typed.javadsl.AskPattern.ask, which would look something like:

AskPattern.ask(
    typedActor,
    replyTo -> new TypedActor.Message(replyTo),
    timeout,
    Adapter.toTyped(getContext().getSystem()).scheduler()
)

The replyTo -> new TypedActor.Message(replyTo) injects the "reply-to" address into the request.

Note that while Patterns.ask returns a Scala future, this returns a Java CompletionStage: you'll most likely want to pipe the returned CompletionStage to the classic actor with

Patterns.pipe(csFromAsk, getContext().dispatcher()).to(self())

Note that blocking a thread while waiting for the Future from Patterns.ask or CompletionStage from AskPattern.ask is not a good idea and can create deadlock.

0
Chad Showalter On

Here is what I landed on. Short version: I used Patterns.askWithReplyTo() as I show below:

Patterns.askWithReplyTo(Adapter.toClassic(typedActorRef),
                        ref -> new TypedActor.Message("cat",Adapter.toTyped(ref)),
                        timeout)

This gives me access to the temporary actor created by the ask pattern, which I transform to a typed actor to allow the Typed actor to reply to this actor instead of directly to the asking actor. This allows the Future to complete.

My complete classic actor example is below. A Typed actor that handles TypedActor.Message and replies with a TypedActor.Reply to the replyTo argument is not shown.

package com.example;

import akka.actor.AbstractActor;
import akka.actor.Props;
import akka.actor.typed.javadsl.Adapter;
import akka.pattern.Patterns;
import akka.util.Timeout;
import scala.compat.java8.FutureConverters;

import java.time.Duration;

public class ClassicAskerActor extends AbstractActor {
    private final akka.actor.typed.ActorRef<TypedActor.Command> typedActorRef;
    static Props props() {
        return Props.create(ClassicAskerActor.class);
    }

    private ClassicAskerActor() {
        this.typedActorRef = Adapter.spawn(getContext().system(), TypedActor.create(), "typedActor");
    }

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

    private void onStringMessage(String message) {
        Timeout timeout = Timeout.create(Duration.ofSeconds(5));
        FutureConverters.toJava(Patterns.askWithReplyTo(Adapter.toClassic(typedActorRef),
                        ref -> new TypedActor.Message("cat",Adapter.toTyped(ref)),
                        timeout))
                .toCompletableFuture()
                .thenAccept(response -> {
                    System.out.println("Response received: " + ((TypedActor.Reply) response).value());
                })
                .exceptionally(ex -> {
                    System.out.println("Failed to get response: " + ex.getMessage());
                    return null;
                });
    }
}

I would be glad for feedback on what I am suggesting here.