Akka broadcast ask doesn't deliver to all routees - scala

I am learning to use broadcast messages in Akka routers. Is there a way to receive responses for ask from all the routees to the router?
I have this sample code.
Master.scala
object Master {
case object brdcst
}
class Master extends Actor {
implicit val timeout = Timeout(5 seconds)
val router: ActorRef = context.actorOf (RoundRobinPool (3).props(Props[Worker]), "router")
override def receive: Receive = {
case brdcst => {
val future = router ? Broadcast(brdcst)
val result = Await.result(future, timeout.duration)
println("result = " + result)
}
}
}
object MasterTest extends App {
val actorSystem = ActorSystem("ActorSystem")
val actor = actorSystem.actorOf(Props[master], "root")
actor ! brdcst
}
Worker.scala
class Worker extends Actor {
val routee = context.actorOf(Props[Worker], "routee")
override def receive: Receive = {
case brdcst => sender() ! self.path.name
}
}
This code gives the following output
result = $a
[INFO] [10/16/2018 21:47:07.484] [ActorSystem-akka.actor.default-dispatcher-2] [akka://ActorSystem/deadLetters] Message [java.lang.String] from Actor[akka://ActorSystem/user/root/router/$a#340358688] to Actor[akka://ActorSystem/deadLetters] was not delivered. [1] dead letters encountered. If this is not an expected behavior, then [Actor[akka://ActorSystem/deadLetters]] may have terminated unexpectedly, This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'.
[INFO] [10/16/2018 21:47:07.504] [ActorSystem-akka.actor.default-dispatcher-10] [akka://ActorSystem/deadLetters] Message [java.lang.String] from Actor[akka://ActorSystem/user/root/router/$b#-151225340] to Actor[akka://ActorSystem/deadLetters] was not delivered. [2] dead letters encountered. If this is not an expected behavior, then [Actor[akka://ActorSystem/deadLetters]] may have terminated unexpectedly, This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'.
So the reply is coming only from one routee. How to get responses from all the routees? (Maybe as a list like result = [$a, $b, $c])

ask (?) creates an internal actor to handle replies. This internal actor handles only a single reply and is automatically shut down: this is the reason you get only the first response, while the responses from the other two routees go to dead letters.
To get the desired behavior, use tell (!) and collect the responses from the routees. For example:
class Master extends Actor {
val numRoutees = 3
val router = context.actorOf(RoundRobinPool(numRoutees).props(Props[Worker]), "router")
def handleMessages(replies: Set[String] = Set()): Receive = {
case brdcst =>
router ! Broadcast(brdcst)
case reply: String =>
val updatedReplies = replies + reply
if (updatedReplies.size == numRoutees) {
println("result = " + updatedReplies.mkString("[", ",", "]"))
}
become(handleMessages(updatedReplies))
}
def receive = handleMessages
}
In the above example, the master encodes the routees' replies as part of its state, using become.
Also, don't use Await in actors.

Related

getting akka dead letters when using pipeTo between two actors

i have a use case in which i have a actor hierarchy
parent -> childABC -> workerchild
Now the worker child works and send its result to its parent(childABC which is a child of parent) and that child actor(childABC) send the result back to parent actor I am using pipeTo and getting dead letters here is my code
parent actor:
final case object GetFinalValue
class MyActor extends Actor{
import context.dispatcher
import akka.pattern.pipe
val log = LoggerFactory.getLogger(this.getClass)
val myManageActor = context.actorOf(Props[ManagerMyActor],"Managemyactor")
implicit val timeout = Timeout(ReadTimeIntervalValue.getInterval(), SECONDS)
override def receive: Receive = {
case GetFinalValue=>
ask(myManageActor,GetValue).pipeTo(sender())
case message =>
log.warn(" Unhandled message received : {}", message)
unhandled(message)
}
}
childABC (acc to example I gave above)
final case object GetValue
class ManagerMyActor extends Actor{
import context.dispatcher
import akka.pattern.pipe
val log = LoggerFactory.getLogger(this.getClass)
val myTokenActor = context.actorOf(Props[TokenMyActor2],"toknMyActor2")
implicit val timeout = Timeout(ReadTimeIntervalValue.getInterval(), SECONDS)
override def receive: Receive = {
case GetValue=>
ask(myTokenActor,CalculateValue).pipeTo(sender())
case message =>
log.warn(" Unhandled message received : {}", message)
unhandled(message)
}
}
child actor:
final case object CalculateValue
class TokenMyActor2 extends Actor{
import context.dispatcher
import akka.pattern.pipe
val log = LoggerFactory.getLogger(this.getClass)
override def receive: Receive = {
case CalculateValue=>
val future = Future{ "get the string"
}
val bac = future.map{result =>
sender ! result
}//.pipeTo(sender())
case message =>
log.warn("Actor MyActor: Unhandled message received : {}", message)
unhandled(message)
}
}
def main(args: Array[String]): Unit = {
implicit val timeout = Timeout(ReadTimeIntervalValue.getInterval(), SECONDS)
val myActor = system.actorOf(Props[MyActor],"myActor")
val future = ask(myActor, GetFinalValue).mapTo[String]
future.map {str =>
log.info ("string is {}",str)
}
Here are the logs:
[INFO] [akkaDeadLetter][01/12/2021 19:17:22.000] [api-akka.actor.default-dispatcher-5] [akka://api/deadLetters] Message [java.lang.String] from Actor[akka://api/user/myActor/Managemyactor/toknMyActor2#1239397461] to Actor[akka://api/deadLetters] was not delivered. [1] dead letters encountered. If this is not an expected behavior then Actor[akka://api/deadLetters] may have terminated unexpectedly. This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'.
[INFO] [akkaDeadLetter][01/12/2021 19:17:41.989] [api-akka.actor.default-dispatcher-7] [akka://api/deadLetters] Message [akka.actor.Status$Failure] from Actor[akka://api/user/myActor#1829301550] to Actor[akka://api/deadLetters] was not delivered. [2] dead letters encountered. If this is not an expected behavior then Actor[akka://api/deadLetters] may have terminated unexpectedly. This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'.
[INFO] [akkaDeadLetter][01/12/2021 19:17:41.996] [api-akka.actor.default-dispatcher-7] [akka://api/deadLetters] Message [akka.actor.Status$Failure] from Actor[akka://api/user/myActor/Managemyactor#-269929265] to Actor[akka://api/deadLetters] was not delivered. [3] dead letters encountered. If this is not an expected behavior then Actor[akka://api/deadLetters] may have terminated unexpectedly. This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'.
Please guide me where am I mistaken, or pipeTo should not be used like this? if so what should i do to make it work
Not sure if it's intended or not but ask(myManageActor,GetValue).pipeTo(sender()) can be implemented as forward.
class MyActor extends Actor {
lazy val myManageActor: ActorRef = ???
override def receive: Receive = {
case GetFinalValue =>
myManageActor.forward(GetValue)
}
}
forward is the same as tell but it preserves the original sender of the messages.
This can be applied to MyActor and ManagerMyActor.
In the case of TokenMyActor2, you should not use
future.map{ result =>
sender ! result
}
as it it breaks akka context encapsulation, as specified in docs
When using future callbacks, such as onComplete, or map such as
thenRun, or thenApply inside actors you need to carefully avoid
closing over the containing actor’s reference, i.e. 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
You should instead rely on Future(???).pipeTo(sender()), which is safe to use with sender().
After applying these changes, the code does work as expected
case object GetFinalValue
case object GetValue
case object CalculateValue
class MyActor extends Actor {
private val myManageActor: ActorRef =
context.actorOf(Props[ManagerMyActor], "myManageActor")
override def receive: Receive = { case GetFinalValue =>
myManageActor.forward(GetValue)
}
}
class ManagerMyActor extends Actor {
private val myTokenActor =
context.actorOf(Props[TokenMyActor2], "toknMyActor2")
override def receive: Receive = { case GetValue =>
myTokenActor.forward(CalculateValue)
}
}
class TokenMyActor2 extends Actor {
import context.dispatcher
override def receive: Receive = { case CalculateValue =>
val future = Future { "get the string" }
future.pipeTo(sender())
}
}
implicit val timeout = Timeout(3, SECONDS)
implicit val system = ActorSystem("adasd")
import system.dispatcher
val myActor = system.actorOf(Props[MyActor], "myActor")
val future = ask(myActor, GetFinalValue).mapTo[String]
future.foreach { str =>
println(s"got $str")
}
Produces got get the string.
As a final note, I'd advise not to use ask pattern within actors. The basic functionality of ask can be easily achieved with just tell and forward. Also the code is shorter and not overloaded with constant need of implicit val timeout
Just to add on top of the great post by #IvanStanislavciuc. You already noticed that you lose the reference to the sender in futures. A simple solution for that will be to keep it up front.
It means that changing in MyActor:
ask(myManageActor,GetValue).pipeTo(sender()) // Won't work
into:
val originalSender = sender()
ask(myTokenActor,CalculateValue).pipeTo(originalSender)
In ManagerMyActor, Change:
ask(myTokenActor,CalculateValue).pipeTo(sender()) // Won't work
into:
val originalSender = sender()
ask(myManageActor,GetValue).pipeTo(originalSender)
And in TokenMyActor2:
val originalSender = sender()
Future{ "get the string" }.pipeTo(originalSender)
Code run at Scastie.

Reply to sender goes to dead letter box

I have the following unit test:
it should "return xml" in new TestScope {
val testProbe: TestProbe = TestProbe()
val someActor = system.actorOf(Props[SomeActor])
testProbe.send(someActor, MakeXmlApiCall())
testProbe.expectMsgPF() {
case Success(message) => {
assert(message == "someMessage")
}
}
}
For the actor with this receive method:
override def receive: Receive = {
case MakeXmlApiCall() => {
val status = Future {"someMessage"}
println("In SomeActor")
status onComplete {
case Success(message) => {
sender ! message
}
}
}
}
"In SomeActor" is printed, which means that control reaches the receive method.
However, I am also getting this message:
[INFO] [12/15/2016 18:42:29.463] [testSystem-akka.actor.default-dispatcher-3] [akka://testSystem/deadLetters] Message [java.lang.String] from Actor[akka://testSystem/user/$a#-1159394947] to Actor[akka://testSystem/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'.
So why is the message sent to the dead-letter rather than the testProbe from the unit test.
Why is this happening?
Referencing sender() (or any actor state) from a callback or operator on a future is not safe, since sender() can have changed.
You should instead use pipe the future back to the sender: status.pipeTo(sender())
In addition, your expectMsgPF() call does not match on what you're actually sending from SomeActor. You're replying with a string, not an instance of Success.

Akka round-robin: Sending response from remote routees to sender

I am using Akka Cluster (version 2.4.10) with few nodes designated for "front-end" role and few others as "workers". The workers are on remote machines. The incoming work is distributed by the front-end actor to workers by round-robin routing. The issue is sending back the response from the "workers" back to the front-end actor. I can see that the work is getting completed by the workers. But the message sent by the workers to front-end does not reach and ends up as dead-letters. I see the below error in the log.
[Cluster-akka.actor.default-dispatcher-21] [akka://Cluster/deadLetters] Message [scala.collection.immutable.$colon$colon] from Actor[akka://Cluster/user] to Actor[akka://Cluster/deadLetters] was not delivered. [6] dead letters encountered.
I have seen this and I am following the same in my code. I have also seen this, but the solution suggested does not apply in this case, because I do not know the routees up-front. It comes through the configuration and it can change. The round-robin router configuration is as below.
akka.actor.deployment {
/frontEnd/hm = {
router = round-robin-group
nr-of-instances = 5
routees.paths = ["/user/hmWorker"]
cluster {
enabled = on
use-role = backend
allow-local-routees = on
}
}
}
The router is instantiated in front-end actor like below.
val router = context.actorOf(FromConfig.props(), name = "hm")
val controller = context.actorOf(Props(classOf[Controller], router))
The controller and the worker codes are below.
// Node 1 : Controller routes requests using round-robin
class Controller(router: ActorRef) extends Actor {
val list = List("a", "b") // Assume this is a big list
val groups = list.grouped(500)
override def receive: Actor.Receive = {
val futures = groups.map(grp => (router ? Message(grp)).mapTo[List[String]]))
val future = Future.sequence(futures).map(_.flatten)
val result = Await.result(future, 50 seconds)
println(s"Result is $result")
}
}
// Node 2
class Worker extends Actor {
override def receive: Actor.Receive = {
case Message(lst) =>
val future: Future[List[String]] = // Do Something asynchronous
future onComplete {
case Success(r) => sender.!(r)(context.parent) // This message is not delivered to Controller actor.
case Failure(th) => // Error handling
}
}
}
Please let me know what I am doing wrong here. Appreciate your help.
You shouldn't use sender() in the callback on a Future. By the time the callback is processed, the sender() is likely referring to something different than it was when you received the message.
Consider either saving the reference outside of the callback first like:
override def receive: Actor.Receive = {
case Message(lst) =>
val future: Future[List[String]] = // Do Something asynchronous
val replyTo: ActorRef = sender()
future onComplete {
case Success(r) => replyTo.!(r)(context.parent) // This message is not delivered to Controller actor.
case Failure(th) => // Error handling
}
}
Or even better, use the pipe pattern:
import akka.pattern.pipe
override def receive: Actor.Receive = {
case Message(lst) =>
val future: Future[List[String]] = // Do Something asynchronous
future.pipeTo(sender())
}

Subscribing multiple actors to Dead Letters in Akka

I am trying to create a simple application that has two actors:
Master actor that handles some App actions
DeadLettersListener that is supposed to handle all dead or unhandled messages
Here is the code that works perfectly:
object Hw extends App {
// creating Master actor
val masterActorSystem = ActorSystem("Master")
val master = masterActorSystem.actorOf(Props[Master], "Master")
// creating Dead Letters listener actor
val deadLettersActorSystem = ActorSystem.create("DeadLettersListener")
val listener = deadLettersActorSystem.actorOf(Props[DeadLettersListener])
// subscribe listener to Master's DeadLetters
masterActorSystem.eventStream.subscribe(listener, classOf[DeadLetter])
masterActorSystem.eventStream.subscribe(listener, classOf[UnhandledMessage])
}
According to the akka manual though, ActorSystem is a heavy object and we should create only one per application. But when I replace these lines:
val deadLettersActorSystem = ActorSystem.create("DeadLettersListener")
val listener = deadLettersActorSystem.actorOf(Props[DeadLettersListener])
with this code:
val listener = masterActorSystem.actorOf(Props[DeadLettersListener], "DeadLettersListener")
The subscription does not work any more and DeadLettersListener is not getting any Dead or Unhandled messages.
Can you please explain what am I doing wrong and give an advice how to subscribe to Dead Letters in this case?
I can't really imagine what are you doing wrong, I created a small example, and it seems to work:
object Hw extends App {
class Master extends Actor {
override def receive: Receive = {
case a => println(s"$a received in $self")
}
}
class DeadLettersListener extends Actor {
override def receive: Actor.Receive = {
case a => println(s"$a received in $self")
}
}
// creating Master actor
val masterActorSystem = ActorSystem("Master")
val master = masterActorSystem.actorOf(Props[Master], "Master")
val listener = masterActorSystem.actorOf(Props[DeadLettersListener])
// subscribe listener to Master's DeadLetters
masterActorSystem.eventStream.subscribe(listener, classOf[DeadLetter])
masterActorSystem.eventStream.subscribe(listener, classOf[UnhandledMessage])
masterActorSystem.actorSelection("/unexistingActor") ! "yo"
}
Could you try it?

How to send iterables between actors or from an actor to a Future?

A future from the main method of a program sends a msg to its actor asking for an iterable object. The actor then creates another future that asks for the iterable object (say an ArrayBuffer) from a remote actor. After receiving the ArrayBuffer from the remote actor, how would the actor send it back to the first future in the main method? It seems creating a local alias of sender and creating a separate case class to represent the iterable does not prevent dead letters from being encountered.
Here is a sample code:
case class SequenceObject(sqnce:Seq[someArrayBuffer])
//...
implicit val timeout = Timeout(10 seconds)
val fut1: Future[Any] = myActor ? iNeedAnArrayBufferObject
fut1.onSuccess {
case listOfItems: SequenceObject => {
//do sth with listofItems.sqnce
}
class myActor extends Actor {
implicit val timeout = Timeout(1 seconds)
def receive = {
case a: iNeedAnArrayBufferObject => {
val originalSender = sender
val fut: Future[Any] = (remoteActor ? a)
fut.onSuccess {
case list: SequenceObject => {
originalSender ! SequenceObject(list.sqnce)
}
}
The remote actor code is:
class ServerActorClass extends Actor {
def receive = {
case a: iNeedAnArrayBufferObject => {
val closer = sender()
closer ! SequenceObject(ArrayBufferObject[information])
}
}
The above does not seem to work. The remote actor and the local actor can communicate and messages are received correctly. However, the iterable object is never send back to fut1. Why is that? Thanks in advance.
Check pipeTo pattern in Ask: Send-And-Receive-Future section