I'm just messing around with some basics to learn akka remoting, and I'm running into an issue where my proxy class sends Identify messages to my backend, but never receives a response back.
I've verified that the backend receives messages sent to the ActorSelection, and I've seen a logging message where the backend actor says it will use xxx for serialization of the ActorIdentity messages. Not sure where I'm getting it wrong.
Here is my proxy class:
package remoting
import akka.actor.{Actor, ActorIdentity, ActorRef, Identify, ReceiveTimeout, Terminated}
import com.typesafe.config.ConfigFactory
import scala.concurrent.duration._
class BackendProxyActor extends Actor {
context.setReceiveTimeout(3.seconds)
val path = createPath
val backendSelection = context.actorSelection(path)
println(f"BackendSelection is $backendSelection")
override def preStart(): Unit = backendSelection ! Identify(1)
override def receive: Receive = {
case ActorIdentity(1, Some(actor)) =>
context.setReceiveTimeout(Duration.Undefined)
context.watch(actor)
context.become(active(actor))
case ActorIdentity(1, None) =>
println("Backend actor not available")
case ReceiveTimeout =>
backendSelection ! Identify(1)
case msg: Any => println(f"Received $msg while identifying backend")
}
def active(backend: ActorRef): Receive = {
case msg: Any => backend ! msg
case Terminated(backend) =>
println("backend terminated, going to identifying state")
context.setReceiveTimeout(3.seconds)
context.become(receive)
}
def createPath: String = {
val config = ConfigFactory.load("frontend").getConfig("backend")
val name = config.getString("name")
val host = config.getString("host")
val port = config.getString("port")
val system = config.getString("system")
val protocol = config.getString("protocol")
f"$protocol://$system#$host:$port/$name"
}
}
Here is my backend class:
package remoting
import akka.actor.{Actor, Identify, PoisonPill}
import com.typesafe.config.ConfigFactory
class BackendActor extends Actor {
val config = ConfigFactory.load("backend")
override def receive: Receive = {
case "stop" => self ! PoisonPill
case msg: Any => println(f"Received $msg")
}
}
My frontend class:
package remoting
import akka.actor.{Actor, Props}
class FrontendActor extends Actor {
val proxy = context.actorOf(Props[BackendProxyActor], "backendProxy")
override def receive: Receive = {
case msg: Any => proxy ! msg
}
}
and finally my App class:
package remoting
import akka.actor.{ActorSystem, Props}
import com.typesafe.config.ConfigFactory
object Main extends App {
val frontendConfig = ConfigFactory.load("frontend")
val frontend = ActorSystem("frontend", frontendConfig)
val frontendActor = frontend.actorOf(Props[FrontendActor], "FrontendActor")
(1 to 20).foreach(i => frontendActor ! f"Msg #$i")
frontendActor ! "stop"
}
My backend is being started in another process, and is run on port 2551, while my frontend is on port 2552.
Related
I have a simple example where I have a route that invokes an actor, however it seems to get stuck in an infinite loop and the http response never comes. I am using akka-actor version 2.6.15 and akka-http version 10.2.4. Here is the sample code, any help is appreciated.
package test
import akka.actor.{Actor, ActorRef, Props}
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.{Route, _}
import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest}
import akka.pattern.ask
import akka.util.Timeout
import org.scalatest.Matchers
import org.scalatest.wordspec.AnyWordSpec
import scala.concurrent.duration.DurationInt
case class TestMessage()
class TestActor extends Actor {
def receive: Receive = {
case _ => "response"
}
}
class AkkaHttpTest extends AnyWordSpec with Matchers with ScalatestRouteTest {
val testActor: ActorRef = system.actorOf(Props(new TestActor()), name = "TestActor")
implicit val timeout: Timeout = 15.seconds
implicit val defaultTimeout = RouteTestTimeout(15.seconds)
val route: Route = {
get {
pathSingleSlash {
complete((testActor ? TestMessage()).mapTo[String])
}
}
}
"Test" should {
"Return text" in {
Get() ~> route ~> check {
println(responseAs[String])
}
}
}
}
To reply to a message in Akka, you have to explicitly send the reply.
In your example:
def receive: Receive = {
case _ =>
sender ! "response"
}
I am trying to setup a simple process with Spark Streaming, using Apache Bahir to connect to Akka. I tried to follow their example together with this older one. I have a simple forwarder actor
class ForwarderActor extends ActorReceiver {
def receive = {
case data: MyData => store(data)
}
}
and I create a stream with
val stream = AkkaUtils.createStream[RSVP](ssc, Props[ForwarderActor], actorName)
the configuration looks like this:
akka {
actor {
provider = "akka.remote.RemoteActorRefProvider"
}
remote {
enabled-transports = ["akka.remote.netty.tcp"]
netty.tcp {
hostname = "localhost"
port = 7777
}
}
}
and my problem is: how do I send messages to the Forwarder actor? Maybe I don't understand how Akka Remote is used in this case. When the app starts, I see a log
[akka.remote.Remoting] Remoting started; listening on addresses :[akka.tcp://test#localhost:7777]
and later on I see
[akka.remote.Remoting] Remoting now listens on addresses: [akka.tcp://streaming-actor-system-0#192.168.192.7:52369]
Which seems to remind to the description in the ScalaDoc:
/**
* A default ActorSystem creator. It will use a unique system name
* (streaming-actor-system-<spark-task-attempt-id>) to start an ActorSystem that supports remote
* communication.
*/
All in all I am not sure how I am supposed to send messages to the Forwarder actor. Thanks for any help!
Akka actors can send messages to other Akka actors running on a remote JVM. So... when the sender actor needs to know the address of the intended receiver actor.
AkkaUtil (Bahir) enables you to create a spark-stream from the messages that a ReceiverActor receives. But, where is is going to receive messages from ? Well... some remote actor. And to send messages this remote actor is going to need the address of your ReceiverActor which is running in your spark-application.
In general, you can not be too sure about the ip which will be running your spark application. So, we will make it so that the actor running with spark will tell the producer actor its reference and request it to send its things.
Just make sure that both applications are written using same version of Scala and are running the same JRE.
Now... lets first write the actor who will be the data source,
import akka.actor.{Actor, ActorRef, ActorLogging, ActorSystem, Props}
import akka.actor.Actor.Receive
import com.typesafe.config.{Config, ConfigFactory}
case class SendMeYourStringsRequest(requesterRef: ActorRef)
case class RequestedString(s: String)
class MyActor extends Actor with ActorLogging {
val theListOfMyStrings = List("one", "two", "three")
override def receive: Receive = {
case SendMeYourStringsRequest(requesterRef) => {
theListOfMyStrings.foreach(s => {
requesterRef ! RequestedString(s)
})
}
}
}
object MyApplication extends App {
val config = ConfigFactory.parseString(
"""
|akka{
| actor {
| provider = remote
| }
| remote {
| enabled-transports = ["akka.remote.netty.tcp"]
| untrusted-mode = off
| netty.tcp {
| hostname="my-ip-address"
| port=18000
| }
| }
|}
""".stripMargin
)
val actorSystem = ActorSystem("my-actor-system", config)
var myActor = actorSystem.actorOf(Props(classOf[MyActor]), "my-actor")
}
Now... lets write our simple spark app,
import akka.actor.{Actor, ActorRef, ActorLogging, ActorSystem, Props}
import akka.actor.Actor.Receive
import com.typesafe.config.{Config, ConfigFactory}
import org.apache.spark.SparkConf
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.akka.{ActorReceiver, AkkaUtils}
case class SendMeYourStringsRequest(requesterRef: ActorRef)
case class RequestedString(s: String)
class YourStringRequesterActor extends ActorReceiver {
def receive = {
case RequestedString(s) => store(s)
}
override def preStart(): Unit = {
val myActorPath = ActorPath.fromString("akka.tcp://my-actor-system#my-ip-address:18000/user/my-actor")
val myActorSelection = context.actorSelection(myActorPath)
myActorSelection ! SendMeYourStringsRequest(self)
}
}
object YourSparkApp {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setAppName("ActorWordCount")
if (!sparkConf.contains("spark.master")) {
sparkConf.setMaster("local[2]")
}
val ssc = new StreamingContext(sparkConf, Seconds(2))
val stringStream = AkkaUtils.createStream[String](
ssc,
Props(classOf[YourStringRequesterActor]),
"your-string-requester-actor"
)
stringStream.foreach(println)
}
}
Note :: Just take care of my-ip-address. If there are any other problems, please let me know in comments.
For router actor with "group mode", we new a series of actors in advance, and then we will use actorOf to associate one router with remote routees.
In fact, it internal will use actorSelection, my question is: how to assure the association already finish?
For actorSelection, we can use resolveOne to make sure the selection is successful, then send message. But how about router actor? See following code, how to assure line2 associate with remote routees before send message to it, just like line line1 does?
package local
import akka.actor._
import akka.routing.RoundRobinGroup
import akka.util.Timeout
import scala.concurrent.duration._
import com.typesafe.config.ConfigFactory
import scala.util.{Failure, Success}
object Local extends App {
val config = ConfigFactory.parseString(s"akka.remote.netty.tcp.port=2251")
val system = ActorSystem("LocalSystem", config.withFallback(ConfigFactory.load()))
system.actorOf(Props[LocalActor], "LocalActor")
val config2 = ConfigFactory.parseString(s"akka.remote.netty.tcp.port=2252")
val system2 = ActorSystem("LocalSystem2", config2.withFallback(ConfigFactory.load()))
system2.actorOf(Props[LocalActor2], "LocalActor2")
}
class LocalActor extends Actor {
def receive = {
case _ => println("hi")
}
}
class LocalActor2 extends Actor {
import scala.concurrent.ExecutionContext.Implicits.global
implicit val timeout = Timeout(5 seconds)
val a = context.actorSelection("akka.tcp://LocalSystem#127.0.0.1:2251/user/LocalActor")
a.resolveOne().onComplete { // line 1
case Success(actor) => println("ready")
a ! 1
case Failure(ex) => println("not ready")
}
val paths = List("akka.tcp://LocalSystem#127.0.0.1:2251/user/LocalActor")
val b = context.actorOf(RoundRobinGroup(paths).props(), "LocalActorRouter") // line 2
b ! 1
def receive = {
case _ =>
}
}
I've followed the example for creating Web Sockets with Scala Play and Akka actors:
https://www.playframework.com/documentation/2.5.x/ScalaWebSockets#Handling-WebSockets-with-Akka-Streams-and-actors
On resume, the controller:
import play.api.mvc._
import play.api.libs.streams._
class Controller1 #Inject() (implicit system: ActorSystem, materializer: Materializer) {
def socket = WebSocket.accept[String, String] { request =>
ActorFlow.actorRef(out => MyWebSocketActor.props(out))
}
And the Actor:
import akka.actor._
object MyWebSocketActor {
def props(out: ActorRef) = Props(new MyWebSocketActor(out))
}
class MyWebSocketActor(out: ActorRef) extends Actor {
def receive = {
case msg: String =>
out ! ("I received your message: " + msg)
}
}
The actors created (one per websocket connection) are child of /user actor. I've created 3 connections and the actor created were:
/user/$b
/user/$c
/user/$d
I want to change the actors' name based in a field of the web socket message. How could i do this?.
You can set the name of the actor as follows:
Create a file BetterActorFlow.scala
package your.package
import akka.actor._
import akka.stream.scaladsl.{Keep, Sink, Source, Flow}
import akka.stream.{Materializer, OverflowStrategy}
object BetterActorFlow {
def actorRef[In, Out](props: ActorRef => Props, bufferSize: Int = 16, overflowStrategy: OverflowStrategy = OverflowStrategy.dropNew, maybeName: Option[String] = None)(implicit factory: ActorRefFactory, mat: Materializer): Flow[In, Out, _] = {
val (outActor, publisher) = Source.actorRef[Out](bufferSize, overflowStrategy)
.toMat(Sink.asPublisher(false))(Keep.both).run()
def flowActorProps: Props = {
Props(new Actor {
val flowActor = context.watch(context.actorOf(props(outActor), "flowActor"))
def receive = {
case Status.Success(_) | Status.Failure(_) => flowActor ! PoisonPill
case Terminated(_) => context.stop(self)
case other => flowActor ! other
}
override def supervisorStrategy = OneForOneStrategy() { case _ => SupervisorStrategy.Stop }
})
}
def actorRefForSink =
maybeName.fold(factory.actorOf(flowActorProps)) { name => factory.actorOf(flowActorProps, name) }
Flow.fromSinkAndSource(Sink.actorRef(actorRefForSink, Status.Success(())), Source.fromPublisher(publisher))
}
}
Use BetterActorFlow instead of ActorFlow:
BetterActorFlow.actorRef(out =>
ChatActor.props(out), 16, OverflowStrategy.dropNew, Some("alicebob"))
This worked for me. The created actor is at user/alicebob (use this with context.system.actorSelection("user/alicebob"))
According to the source code of ActorFlow, it is currently not possible to thange the name of the actual actor spawned for a connection (line 38):
Sink.actorRef(factory.actorOf(Props(new Actor { ... }) /*, name parameter is omitted */)
However, ActorFlow.actorRef accepts an implicit ActorRefFactory, which is implicit system: ActorSystem in all cases in your code. As we know, there are 2 most common ActorRefFactories: ActorSystem and ActorContext. You can modify your code in such a way that each time a connection is accepted another dummy actor would spawn with your preferred name (e.g. myActor1), and pass this new actor's context to ActorFlow.actorRef instead. In return, actors for connections would be named as follows:
/user/myActor1/$a
/user/myActor2/$a
etc
In the spray-can HTTP server example, when I run the server, I don't see any "Bound" message showing up in the log:
https://github.com/spray/spray/tree/master/examples/spray-can/simple-http-server/src/main
However, if I try to create my own server, and ignore the Bound message I get them going to dead-letters:
$ sbt run
[info] Set current project to pingpong (in build file:/D:/Projects/pingpong/)
[info] Updating {file:/D:/Projects/pingpong/}pingpong...
[info] Resolving jline#jline;2.12.1 ...
[info] Done updating.
[info] Running drozdyuk.pingpong.Main
[INFO] [08/03/2015 20:05:47.246] [default-akka.actor.default-dispatcher-2] [akka
://default/user/IO-HTTP/listener-0] Bound to localhost/127.0.0.1:8081
[INFO] [08/03/2015 20:05:47.246] [default-akka.actor.default-dispatcher-2] [akka
://default/deadLetters] Message [akka.io.Tcp$Bound] from Actor[akka://default/us
er/IO-HTTP/listener-0#-892116855] to Actor[akka://default/deadLetters] was not d
elivered. [1] dead letters encountered. This logging can be turned off or adjust
ed with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letter
s-during-shutdown'.
My question is mainly out of curiosity, how does the Bound message get handled in the example? Is it some kind of logging that swallows them or something else?
Code for my Main.scala:
package drozdyuk.pingpong
import akka.actor.{ActorSystem, Props}
import akka.io.IO
import spray.can.Http
object Main extends App {
implicit val system = ActorSystem()
// the handler actor replies to incoming HttpRequests
val handler = system.actorOf(Props[WebService], name = "handler")
val PORT = 8081
val DOMAIN = "localhost"
IO(Http) ! Http.Bind(handler, interface = DOMAIN, port = PORT)
}
and my WebService.scala:
package drozdyuk.pingpong
import akka.actor._
import spray.http.HttpMethods.{GET}
import spray.can.Http
import spray.http.{HttpRequest, HttpResponse, Uri}
class WebService extends Actor with ActorLogging {
def receive = {
// New connection - register self as handler
case _: Http.Connected => sender ! Http.Register(self)
case HttpRequest(GET, Uri.Path("/ping"), _, _, _) =>
sender ! HttpResponse(entity = "pong")
case HttpRequest(GET, Uri.Path("/pong"), _, _, _) =>
sender ! HttpResponse(entity = "ping")
case _: HttpRequest => sender ! HttpResponse(status = 404, entity = "Unknown resource!")
}
}
You could use another version of tell in your Main:
IO(Http).tell(Http.Bind(handler, interface = DOMAIN, port = PORT), sender = handler)
And then handle Bound message in your WebService:
class WebService extends Actor with ActorLogging {
def receive = {
case Http.Bound(address) =>
//all other cases...
}
}
In your case you are using
def !(message: Any)(implicit sender: ActorRef = Actor.noSender): Unit
and there is no implicit sender in scope of Main so default noSender will be used.
I tried this and it works great for me:
import akka.actor._
import akka.io._
import spray.can.Http
import spray.http.HttpResponse
import spray.http.HttpRequest
import spray.http.Uri
import spray.http.HttpMethods.{GET}
class WebService extends Actor with ActorLogging {
def receive = {
case _: Http.Connected => sender ! Http.Register(self)
case _: HttpRequest => sender ! HttpResponse(status = 404, entity = "Unknown resource!")
case HttpRequest(GET, Uri.Path("/ping"), _, _, _) =>
sender ! HttpResponse(entity = "pong")
//all other cases...
}
}
object TestSpray extends App {
implicit val system = ActorSystem()
val myListener: ActorRef = system.actorOf(Props[WebService], name = "handler")
IO(Http) ! Http.Bind(myListener, interface = "localhost", port = 8080)
}