Scala 2.12 using Akka here. When one of my actors receives a particular type of message (say, Fizzbuzz), I want it to forward that message on to a small number of other actors, exactly as-is:
I tried:
class Foo extends Actor {
override def receive: Receive = {
case Bar =>
println("Bar!")
case Whitstle =>
println("Whistle!")
case Fizzbuzz =>
val actor1 = context.actorSelection("/user/a1")
val actor2 = context.actorSelection("/user/a2")
val actor3 = context.actorSelection("/user/a3")
actor1 ! _
actor2 ! _
actor3 ! _
}
}
And while that compiles and doesn't throw any exceptions, it doesn't work (none of the 3 actors ever receive the Fizzbuzz message). Any ideas?
In the receive block, collect your msg in a variable and then forward that msg to other actors. Please refer the code below :-
class Foo extends Actor {
override def receive: Receive = {
case Bar =>
println("Bar!")
case Whitstle =>
println("Whistle!")
case msg : Fizzbuzz =>
val actor1 = context.actorSelection("/user/a1")
val actor2 = context.actorSelection("/user/a2")
val actor3 = context.actorSelection("/user/a3")
actor1 ! msg
actor2 ! msg
actor3 ! msg
}
}
This should solve your problem. Please let me know if any doubt persists.
Related
I'm trying to create simple tcp server with help of akka.
My main
import java.net.InetSocketAddress
import akka.actor._
import akka.io._
import Tcp._
object Main extends App {
implicit val actorSystem: ActorSystem = ActorSystem()
val serverRef = actorSystem.actorOf(server.Server.props)
val tcpManager = IO(Tcp)
tcpManager ! Bind(serverRef, new InetSocketAddress("localhost", 8099))
}
My server actor:
package server
import akka.actor._
import akka.io.Tcp
import Tcp._
object Server {
def props = Props(new Server())
class SimplisticHandler extends Actor {
def receive = {
case Received(data) => sender() ! Write(data)
case PeerClosed => context.stop(self)
}
}
}
class Server extends Actor {
override def receive: Receive = {
case b # Bound(localAddress) =>
println(s"connected to ${localAddress.toString}")
context.parent ! b
case CommandFailed(_: Bind) => context.stop(self)
case c # Connected(remote, local) =>
val handler = context.actorOf(Server.props)
val connection = sender()
connection ! Register(handler)
case msg =>
println("Unknown message")
}
}
But when i run it i get dead letter log event:
table [INFO] [11/08/2019 18:34:11.558]
[default-akka.actor.default-dispatcher-6] [akka://default/deadLetters]
Message [akka.io.Tcp$Bound] from
Actor[akka://default/system/IO-TCP/selectors/$a/0#-176629096] to
Actor[akka://default/deadLetters] was not delivered. [1] dead letters
encountered. If this is not an expected behavior then
Actor[akka://default/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'.
The Bind message should be sent from an actor. That actor will eventually receive Bound message from TCP manager (or CommandFailed). In this case, Server actor should send Bind message. For instance:
class Server extends Actor {
IO(Tcp) ! Bind(self, new InetSocketAddress("localhost", 8099)) //<--------
override def receive: Receive = {
case b # Bound(localAddress) =>
println(s"connected to ${localAddress.toString}")
context.parent ! b
case CommandFailed(_: Bind) => context.stop(self)
case c # Connected(remote, local) =>
val handler = context.actorOf(Server.props)
val connection = sender()
connection ! Register(handler)
case msg =>
println("Unknown message")
}
}
I have two actors coded as follows.
class Actor1 extends Actor {
val r : ActorRef = actorOf (Props[Actor2], "master")
def receive: Receive = {
case class Mul (a,b) =>
r ! CalcMul (a,b)
case class MulReply (ans) =>
println("Multiply result : " + ans)
// want to send "ans" value to testObject..
}
}
class Actor2 extends Actor {
def receive: Receive = {
case class CalcMul (a,b) =>
sender ! MulReply (a*b)
}
}
object testObject extends App {
val a = ActorSystem("ActorSystem").actorOf(Props[Actor1], "root")
a ! CalcMul (5, 15)
// how to receive "ans" value here?
}
I am able to receive and print the result in Actor1 but need those values in testObject so I can use them for future operations. Cannot have a receive method in testObject as done to receive a message in Actor1 from Actor2, so cannot send them with tell method.
As you want to receive a response from an actor you can use ask pattern for this purpose.
import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import akka.pattern._
import akka.util.Timeout
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.concurrent.duration.SECONDS
case class CalsMul(a: Int, b: Int)
class Actor1 extends Actor {
val r: ActorRef = context.actorOf(Props[Actor2], "master")
def receive: Receive = {
case req: CalsMul =>
println("received message by Actor1")
r forward req
}
}
class Actor2 extends Actor {
def receive: Receive = {
case request: CalsMul =>
println("received message by Actor2")
Future.successful(request.a * request.b) pipeTo sender
}
}
object testObject extends App {
implicit val system: ActorSystem = ActorSystem("ActorSystem")
val a: ActorRef = system.actorOf(Props[Actor1], "root")
implicit val timeout: Timeout = Timeout(20, SECONDS)
println(system, "sending message to Actor1")
val ans: Future[Int] = (a ? CalsMul(5, 15)).mapTo[Int] // as you are returning the multiplication of a*b
ans.foreach(println)
}
Note: CPU bound operations are not advised to use with actors it can have adverse effect on the performance of your application
I have a play(2.4.2 which has akka 2.4.18) application in which I am using akka actors for uploading a file. I have a parent supervisor Actor with this kind of hierarchy
UploadSupervisor ---child---> UploadActor ---child--->
DataWriteActor & MetaWriteActor
The leaf actors MetaWriteActor & DataWriteActor does the actual writing. A very simplified version of my code is below:
First I have a actor supervisor:
class UploadSupervisor extends Actor {
val uploadActor = context.actorOf(Props(new UploadActor), "UploadActor")
override def supervisorStrategy = OneForOneStrategy() {
case _: Throwable => Restart
}
override def receive: Receive = {
case data: Data => uploadActor ! data
case meta: MetaInfo => uploadActor ! meta
//How do I send response outside of actor system?
case dataSuccess: DataUploadResponse => ??? //Line 10
case metaSuccess: MetaUploadResponse => ??? //Line 11
}
object UploadSupervisor {
val uploadSupervisor = Akka.system
.actorOf(Props(new UploadSupervisor), "UploadSupervisor")
}
//Request & Response case classes
case class Data(content: String)
case class MetaInfo(id: String, createdDate: Timestamp)
case class DataUploadResponse(location: String)
case class MetaUploadResponse(location: String)
UploadActor:-
class UploadActor extends Actor {
val dataWriteActor = context.actorOf(Props(new DataWriteActor), "dataWriteActor")
val metaWriteActor = context.actorOf(Props(new MetaWriteActor), "UploadActor")
override def receive = {
case data: Data => dataWriteActor ! data
case meta: MetaInfo => metaWriteActor ! meta
case dataResp: DataUploadResponse => context.parent ! dataResp
case metaResp: MetaUploadResponse => context.parent ! metaResp
}
}
DataWriteActor :
class DataWriteActor extends Actor {
case data: Data => //Do the writing
println("data write completed")
sender() ! DataUploadResponse("someLocation")
}
MetaWriteActor
class MetaWriteActor extends Actor {
case meta: MetaInfo=> //Do the writing
println(" meta info writing completed")
sender() ! MetaUploadResponse("someOtherLocation")
}
Somewhere outside Actor system:-
implicit val timeout = Timeout(10 seconds)
val f1 = UploadSupervisor.uploadSupervisor ? Data("Hello Akka").mapTo(implicitly[scala.reflect.ClassTag[DataUploadResponse]])
val f2 = UploadSupervisor.uploadSupervisor ? MetaInfo("1234", new Timestamp(new Date().getTime).mapTo(implicitly[scala.reflect.ClassTag[MetaUploadResponse]])
//Do something with futures
The question is how to send the response outside the actor system? Because in Line 10 & 11, I can't use sender ! msg because the current sender is the UploadActor.
You could keep in UploadSupervisor references to the initial senders:
class UploadSupervisor extends Actor {
val uploadActor = context.actorOf(Props[UploadActor], "UploadActor")
override val supervisorStrategy = OneForOneStrategy() {
case _ => Restart
}
var dataSender: Option[ActorRef] = None
var metaSender: Option[ActorRef] = None
def receive = {
case data: Data =>
val s = sender
dataSender = Option(s)
uploadActor ! data
case meta: MetaInfo =>
val s = sender
metaSender = Option(s)
uploadActor ! meta
case dataSuccess: DataUploadResponse =>
dataSender.foreach(_ ! dataSuccess)
case metaSuccess: MetaUploadResponse =>
metaSender.foreach(_ ! metaSuccess)
}
}
To send messages to UploadSupervisor:
implicit val timeout = Timeout(10 seconds)
val f1 = (UploadSupervisor.uploadSupervisor ? Data("Hello Akka")).mapTo[DataUploadResponse]
val f2 = (UploadSupervisor.uploadSupervisor ? MetaInfo("1234", new Timestamp(new Date().getTime)).mapTo[MetaUploadResponse]
The above assumes that you're sending one Data message and one MetaInfo message to UploadSupervisor at a time. This approach will break down if you send multiple Data and MetaInfo messages and expect concurrent replies. A more general solution is to include the reference to the initial sender in additional case classes that wrap your existing case classes, passing this reference through your actor hierarchy:
case class DataMsg(data: Data, target: ActorRef)
case class MetaInfoMsg(metaInfo: MetaInfo, target: ActorRef)
case class DataUploadMsg(response: DataUploadResponse, target: ActorRef)
case class MetaUploadMsg(response: MetaUploadResponse, target: ActorRef)
class UploadSupervisor extends Actor {
val uploadActor = context.actorOf(Props[UploadActor], "UploadActor")
override val supervisorStrategy = OneForOneStrategy() {
case _ => Restart
}
def receive = {
case data: Data =>
val s = sender
uploadActor ! DataMsg(data, s)
case meta: MetaInfo =>
val s = sender
uploadActor ! MetaInfoMsg(meta, s)
case DataUploadMsg(response, target) =>
target ! response
case MetaUploadMsg(response, target) =>
target ! response
}
}
The UploadActor:
class UploadActor extends Actor {
val dataWriteActor = context.actorOf(Props[DataWriteActor], "dataWriteActor")
val metaWriteActor = context.actorOf(Props[MetaWriteActor], "UploadActor")
def receive = {
case data: DataMsg => dataWriteActor ! data
case meta: MetaInfoMsg => metaWriteActor ! meta
case dataResp: DataUploadMsg => context.parent ! dataResp
case metaResp: MetaUploadMsg => context.parent ! metaResp
}
}
The writers:
class DataWriteActor extends Actor {
def receive = {
case DataMsg(data, target) =>
// do the writing
println("data write completed")
sender ! DataUploadMsg(DataUploadResponse("someLocation"), target)
}
}
class MetaWriteActor extends Actor {
def receive = {
case MetaInfoMsg(meta, target) =>
// do the writing
println("meta info writing completed")
sender ! MetaUploadMsg(MetaUploadResponse("someOtherLocation"), target)
}
}
I am designing an actor system. centerActor need to wait result from both actor1 and actor2 to response to the client.
In the centerActor:
def receive: Receive ={
case request => {
implicit val timeout = Timeout(1.seconds)
val ask1 = actor1 ? Update1.mapTo[Int]
val ask2 = actor2 ? Update2.mapTo[String]
val response =
(for(x <- ask1; y <- ask2) yield Done).recover{case _ => FailReply}
response pipeTo sender
}
}
In this design, my sender(client) cannot receive Done message. I don't know why.
So I am thinking about the second design:
def receive: Receive = {
implicit val timeout = Timeout(1.seconds)
case request => {
val ask1 = actor1 ? Update1.mapTo[Int]
val ask2 = actor2 ? Update2.mapTo[String]
val response =
(for(x <- ask1; y <- ask2) yield Done).recover{case _ => FailReply})
response pipeTo self
context.become(waiting(sender))
}
}
def waiting(s: ActorRef) = {
case Done =>
s ! Done
unstashAll()
context.unbecome()
case FailReply => s ! FailReply
case _ => stash()
}
So, when sending a message using the ask pattern (actor ? msg), it creates a "one-off actor" behind the scenes.
My question is - Is it possible to send this temporary actor a message using actorSelection?
For example, the following code works well:
object Test extends App {
case class WrappedMsg(msg: String, replyTo: ActorRef)
class Source(target: ActorRef) extends Actor {
def receive = { case _ => } // doesn't matter
implicit val execution = context.dispatcher
implicit val timeout = Timeout(5.seconds)
val middleware = context.actorOf(Props(new Middleware(target)))
(middleware ? "Something").mapTo[String].onComplete {
case Success(msg) => println("Success: " + msg)
case Failure(err) => println("Failure: " + err)
}
}
class Middleware(target: ActorRef) extends Actor {
def receive = {
case msg: String =>
val wrappedMsg = WrappedMsg(replyTo = sender(), msg = msg)
target ! wrappedMsg
}
}
class Target extends Actor {
def receive = {
case wrappedMsg: WrappedMsg => wrappedMsg.replyTo ! "Received"
}
}
val system = ActorSystem()
val target = system.actorOf(Props(new Target))
val source = system.actorOf(Props(new Source(target)))
}
But, if I make the following changes, in order to use the actor url instead of ActorRef, it fails:
case class WrappedMsg(msg: String, replyTo: String)
...
val wrappedMsg = WrappedMsg(replyTo = sender().path.toSerializationFormat, msg = msg)
...
case wrappedMsg: WrappedMsg => context.actorSelection(wrappedMsg.replyTo) ! "Received"
Thanks
Thanks to #Zernike (see the comments in the question), we've found out that in Akka 2.3.12 it works well - the temporary actor is resolved using actorSelection. (The original code that caused the failure was tested in Akka 2.3.6)