lucataglia
lucataglia

Reputation: 792

Akka Typed context.messageAdapter does not manage responses correctly

I'm trying to migrate a personal project from Akka Classic to Akka Typed and I'm having some troubles with messageAdapters.

Here below the code to reproduce the behavior that is causing me some troubles:

object Example extends App {

  val system = ActorSystem(MyActor(), "system")

  system ! MyActor.Msg("Foo")          // 3 chars lenght
  system ! MyActor.Msg("FooBarOsdAsd") // 12 chars lenght



  object MyActor {
    sealed trait Command

    case class Msg(payload: String)                                   extends Command
    case class Question(payload: String, replyTo: ActorRef[Response]) extends Command
    case class WrappedResponseOne(response: Response)                 extends Command
    case class WrappedResponseTwo(response: Response)                 extends Command

    sealed trait Response
    case object Yes extends Response
    case object No  extends Response

    def apply(): Behavior[Command] =
      Behaviors.setup { context =>
        new MyActor(context).stateless()
      }
  }

  // I need to have a private class that implement a method to expose the wanted behavior
  private class MyActor(context: ActorContext[MyActor.Command]) {

    // This is the adapter I want to use
    private val wrappedOne: ActorRef[MyActor.Response] =
      context.messageAdapter[MyActor.Response](MyActor.WrappedResponseOne)

    // This variable is unused 
    private val wrappedTwo: ActorRef[MyActor.Response] =
      context.messageAdapter[MyActor.Response](MyActor.WrappedResponseTwo)

    def stateless(): Behaviors.Receive[MyActor.Command] = Behaviors.receiveMessage[MyActor.Command] {
      case MyActor.Msg(payload) =>
        context.log.debug(s"$payload")
        context.self ! MyActor.Question(payload, wrappedOne)
        Behaviors.same

      case MyActor.Question(payload, replyTo) =>
        if (payload.length > 10) replyTo ! MyActor.No else replyTo ! MyActor.Yes
        Behaviors.same

      case MyActor.WrappedResponseOne(response) =>
        response match {
          case MyActor.Yes =>
            context.log.info("YES")
            Behaviors.same

          case MyActor.No =>
            context.log.info("NO")
            Behaviors.same
        }

      case msg =>
        context.log.debug(s"$msg")
        Behaviors.unhandled
    }
  }
}

Here I'm defining two messageAdapters: wrappedOne and wrappedTwo. When the actor got a MyActor.Msg I want that the responses will be handled by wrappedOne (leaving wrappedTwo completely unused). I can't get why my code let wrappedTwo manage the responses. Here below is the output from the console.

SLF4J: See also http://www.slf4j.org/codes.html#substituteLogger
22:57:38.879 [system-akka.actor.default-dispatcher-3] DEBUG exlude.ActorAdapter$MyActor - Foo
22:57:38.881 [system-akka.actor.default-dispatcher-3] DEBUG exlude.ActorAdapter$MyActor - FooBarOsdAsd
22:57:38.890 [system-akka.actor.default-dispatcher-3] DEBUG exlude.ActorAdapter$MyActor - WrappedResponseTwo(Yes)
22:57:38.891 [system-akka.actor.default-dispatcher-3] DEBUG exlude.ActorAdapter$MyActor - WrappedResponseTwo(No)
22:57:38.904 [system-akka.actor.default-dispatcher-3] INFO  akka.actor.LocalActorRef - Message [exlude.ActorAdapter$MyActor$WrappedResponseTwo] to Actor[akka://system/user] was unhandled. [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'.
22:57:38.907 [system-akka.actor.default-dispatcher-3] INFO  akka.actor.LocalActorRef - Message [exlude.ActorAdapter$MyActor$WrappedResponseTwo] to Actor[akka://system/user] was unhandled. [2] 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'.

Is there something wrong with how I'm defining the adapters?

Upvotes: 0

Views: 273

Answers (1)

Levi Ramsey
Levi Ramsey

Reputation: 20561

That behavior is by design: only one message adapter for a given incoming message type can be registered at a time. This prevents unbounded growth in the number of adapters if (e.g.) the adapter is being registered in response to messages. The last adapter wins.

See docs and note that if A is a subclass of B, adapters for A and B can both be registered, but since the adapters are tested last-registered-first, if the B adapter is registered after the A adapter, it will shadow the A adapter.

For situations where there's at-most-one response per request, context.ask may be a better fit.

If you really need multiple adaptations of the same message types, spawning a child actor for each adaptation is probably the way to go; having to do this may be a sign that your messaging protocols could use a rethink.

Upvotes: 1

Related Questions