3

consider the following example:

case class Payload(message: String, async: Boolean)

class EchoActor extends Actor {
  override def receive: Receive = {
    case Payload(message, async) =>
      if (async) Future {
        println(s"from: $sender")
        sender ! message
      } else {
        println(s"from: $sender")
        sender ! message
      }
  }
}

def main(args: Array[String]): Unit = {
  val system = ActorSystem("demo")
  val echo = system.actorOf(Props[EchoActor])
  implicit val timeout = Timeout(2 seconds)

  (echo ? Payload("Hello", async = false)).mapTo[String].foreach(println(_))
  (echo ? Payload("Async Hello", async = true)).mapTo[String].foreach(println(_))

  StdIn.readLine()
  system.terminate()
}

console output:

from: Actor[akka://demo/temp/$a]
Hello
from: Actor[akka://demo/deadLetters]
[INFO] [04/13/2017 19:56:58.516] [demo-akka.actor.default-dispatcher-4] [akka://demo/deadLetters] Message [java.lang.String] from Actor[akka://demo/user/$a#2112869650] to Actor[akka://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'.

i.e. the sender points to deadLetters when accessing it from another thread.

What is the reason behind that? is it a bug?

although, we can hold a reference to the actual sender to make it work:

if (async) {
  val currentSender = sender()
  Future {
    println(s"from: $currentSender")
    currentSender ! message
  }
}

but... isn't there some better approach?

FaNaJ
  • 1,329
  • 1
  • 16
  • 39

2 Answers2

7

This is not a bug, but a documented behavior -

http://doc.akka.io/docs/akka/2.5.0/scala/actors.html#Send_messages

Using Future means invoking an anonymous function which is not an instance of the Actor class, therefor your sender() ref is mapped to the deadLetters mailbox

Sheinbergon
  • 2,875
  • 1
  • 15
  • 26
2

The better approach is the pipe pattern.

import akka.pattern.pipe

class EchoActor extends Actor {
  override def receive: Receive = {
    case Payload(message, async) =>
      if (async) {
        Future {
          message
        }.pipeTo(sender) 
      } else {
        sender ! message
      }
  }
}

The issue is that sender is a function and its value is only valid if called on the same thread that is processing the incoming message. When you call sender within a future, it's being called from another thread and at another point in time, notably after the actor's receive function has already returned.

pipeTo captures the value of the current sender on the same thread as the actor and before the receive function has returned. This is effectively the same as your approach with the currentSender value.

Ryan
  • 7,227
  • 5
  • 29
  • 40
  • Btw. your code example still accesses `sender` asynchronously inside the `println`. A better solution would be to put it into a `val` before the `Future` block and refer to that inside the `Future` block. – jrudolph Apr 19 '17 at 15:15