Get the chosen actor in a RoundRobinPool? - scala

If I had a RoundRobinPool like this
val actorPoolRef = AkkaConfig.actorSystem.actorOf(RoundRobinPool(100).props(Props[MyService]))
and a handler
def requestHandler(request: HttpRequest): Future[HttpResponse] = {
val promise = Promise[HttpResponse]()
promise.completeWith(actorPoolRef ? request)
promise.future
}
Is there any way I can
get the exact actor reference from the scope of def requestHandler, or
send a follow-up message to the same actor that just handled the request

you can do by using the akka.pattern.ask to request the actor reference from a RoundRobinPool of actors, returning the self.path as an Option and wrapping the response as Option[ActorPath]. To clarify what I am saying I build this simple proof of concept:
import akka.actor.{Actor, ActorLogging, ActorPath, ActorSystem, Props}
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import akka.pattern.ask
import akka.routing.RoundRobinPool
import akka.util.Timeout
import scala.concurrent.Future
import scala.concurrent.duration._
object BasicRoundRobinHttpServer {
def main(args: Array[String]): Unit = {
run()
}
def run() = {
implicit val system = ActorSystem("BasicRoundRobinHttpServer")
import system.dispatcher
implicit val timeout = Timeout(3 seconds)
val myServiceActor = system.actorOf(RoundRobinPool(5).props(Props[MyService]), "myServiceActor")
val simpleRoute: Route =
(path("reference") & get) {
val validResponseFuture: Option[Future[HttpResponse]] = {
// construct the HTTP response
val actorPathResponse: Future[Option[ActorPath]] = (myServiceActor ? "reference").mapTo[Option[ActorPath]]
Option(actorPathResponse.map(ref => HttpResponse(
StatusCodes.OK,
entity = HttpEntity(
ContentTypes.`text/html(UTF-8)`,
s"""
|<html>
| <body>I got the actor reference: ${ref} </body>
|</html>
|""".stripMargin
))))
}
val entityFuture: Future[HttpResponse] = validResponseFuture.getOrElse(Future(HttpResponse(StatusCodes.BadRequest)))
complete(entityFuture)
}
println("http GET localhost:8080/reference")
Http().newServerAt("localhost", 8080).bind(simpleRoute)
}
}
class MyService extends Actor with ActorLogging {
override def receive: Receive = {
case "reference" =>
log.info(s"request reference at actor: ${self}")
sender() ! Option(self.path)
case message =>
log.info(s"unknown message: ${message.toString}")
}
}
requesting the address $ http GET localhost:8080/reference from the browser or using any HTTP requester several times you get actor reference $a, $b, etc...
// first time
<html>
<body>I got the actor reference: Some(akka://BasicRoundRobinHttpServer/user/myServiceActor/$a) </body>
</html>
// second time
<html>
<body>I got the actor reference: Some(akka://BasicRoundRobinHttpServer/user/myServiceActor/$b) </body>
</html>
...

Related

Stuck in infinite loop with simple akka-http route test with actors

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"
}

items fail to be processed in Akka streams app that uses Source.queues and Sink.queues in a flow

I am trying to create an (Akka HTTP) stream procsesing flow using the classes akka.stream.scaladsl.Source and Sink queues.
I am using a queue because I have a processing step in my flow that issues http requests and I want this step to take as many
items off the queue as there are max-open-requests, and stop taking off the queue once max-open-requests are in flight.
The result is that backpressure is applied when my connection pool is overloaded.
Below, I have a very simplified test that reflects the main logic of my app. In the test 'Stress Spec' (below)
I am simulating a number of simultaneous connections via which I will send a 'Source' of 'Requesto' objects
to the getResponses method of the class ServiceImpl.
In the processing step 'pullOffSinkQueue' you will note that I am incrementing a counter to see how many items
I have pulled off the queue.
The test will send Serviceimpl a set of requests whose cardinality is set to equal
streamedRequestsPerConnection * numSimultaneousConnections.
When I send 20 requests my test passes fine. In particular the count of requests pulled off the
Sink.queue will be equal to the number of requests I send out. However, if
I increase the number of requests I send to above 50 or so, I see consistent failures in the test.
I get a message such as the one below
180 was not equal to 200
ScalaTestFailureLocation: com.foo.StressSpec at (StressSpec.scala:116)
Expected :200
Actual :180
<Click to see difference>
This indicates that the number of items pulled off the queue does not equal the number of items put on the queue.
I have a feeling this might be due to the fact that my test is not properly waiting for all items put into the stream
to be processed. If anyone has any suggestions, I'd be all ears ! Code is below.
package com.foo
import java.util.concurrent.atomic.AtomicInteger
import akka.stream.ActorAttributes.supervisionStrategy
import akka.stream.{Attributes, Materializer, QueueOfferResult}
import akka.stream.Supervision.resumingDecider
import akka.stream.scaladsl.{Flow, Keep, Sink, Source}
import scala.concurrent.{ExecutionContext, Future}
import akka.NotUsed
import akka.actor.ActorSystem
import akka.event.{Logging, LoggingAdapter}
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.{Sink, Source}
import org.scalatest.mockito.MockitoSugar
import org.scalatest.{FunSuite, Matchers}
import scala.collection.immutable
import scala.concurrent.duration._
import scala.concurrent.{Await, Future, _}
final case class Responso()
final case class Requesto()
object Handler {
val dbRequestCounter = new AtomicInteger(0)
}
class Handler(implicit ec: ExecutionContext, mat: Materializer) {
import Handler._
private val source =
Source.queue[(Requesto, String)](8, akka.stream.OverflowStrategy.backpressure)
private val sink =
Sink.queue[(Requesto, String)]().withAttributes(Attributes.inputBuffer(8, 8))
private val (sourceQueue, sinkQueue) = source.toMat(sink)(Keep.both).run()
def placeOnSourceQueue(ar: Requesto): Future[QueueOfferResult] = {
sourceQueue.offer((ar, "foo"))
}
def pullOffSinkQueue(qofr: QueueOfferResult): Future[Responso] = {
dbRequestCounter.incrementAndGet()
qofr match {
case QueueOfferResult.Enqueued =>
sinkQueue.pull().flatMap { maybeRequestPair: Option[(Requesto, String)] =>
Future.successful(Responso())
}
case error =>
println("enqueuing error: " + error)
Future.failed(new RuntimeException("enqueuing error: " + error))
}
}
}
class ServiceImpl(readHandler: Handler, writeHandler: Handler)
(implicit log: LoggingAdapter, mat: Materializer) {
private val readAttributeFlow: Flow[Requesto, Responso, NotUsed] = {
Flow[Requesto]
.mapAsyncUnordered(1)(readHandler.placeOnSourceQueue)
.mapAsyncUnordered(1)(readHandler.pullOffSinkQueue)
}
def getResponses(request: Source[Requesto, NotUsed]): Source[Responso, NotUsed] =
request
.via(readAttributeFlow)
.withAttributes(supervisionStrategy(resumingDecider))
}
class StressSpec
extends FunSuite
with MockitoSugar
with Matchers {
val streamedRequestsPerConnection = 10
val numSimultaneousConnections = 20
implicit val actorSystem: ActorSystem = ActorSystem()
implicit val materializer: ActorMaterializer = ActorMaterializer()
implicit val log: LoggingAdapter = Logging(actorSystem.eventStream, "test")
implicit val ec: ExecutionContext = actorSystem.dispatcher
import Handler._
lazy val requestHandler = new Handler()
lazy val svc: ServiceImpl =
new ServiceImpl(requestHandler, requestHandler)
test("can handle lots of simultaneous read requests") {
val totalExpected = streamedRequestsPerConnection * numSimultaneousConnections
def sendRequestAndAwaitResponse(): Unit = {
def getResponses(i: Integer) = {
val requestStream: Source[Requesto, NotUsed] =
Source(1 to streamedRequestsPerConnection)
.map { i =>
Requesto()
}
svc.getResponses(requestStream).runWith(Sink.seq)
}
val responses: immutable.Seq[Future[immutable.Seq[Responso]]] =
(1 to numSimultaneousConnections).map { getResponses(_) }
val flattenedResponses: Future[immutable.Seq[Responso]] =
Future.sequence(responses).map(_.flatten)
Await.ready(flattenedResponses, 1000.seconds).value.get
}
sendRequestAndAwaitResponse()
dbRequestCounter.get shouldBe(totalExpected)
}
}

Remote actor not responding to Identify?

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.

Scala compiler can't find the unmarshalling implicits in route declaration

I'm trying to build a REST server using this tutorial:
https://spindance.com/reactive-rest-services-akka-http/
However, having reached the "Responding with JSON" section, I've noticed that my code doesn't compile, and I can't make POST requests. This is the error that I'm getting:
Error:(59, 18) could not find implicit value for parameter um: akka.http.scaladsl.unmarshalling.FromRequestUnmarshaller[Health]
entity(as[Health]) { statusReport =>
^
Having looked at other tutorials, I've found out that you need to include an object containing an implicit variable for the class that I'm trying to unmarshall. I did that, and I even imported the httpx library, but I'm still getting this error. My code is given below.
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import akka.stream.ActorMaterializer
import akka.pattern.ask
import akka.util.Timeout
import spray.json._
import DefaultJsonProtocol._
import spray.httpx.SprayJsonSupport.sprayJsonUnmarshaller
import scala.concurrent.duration._
import scala.io.StdIn
object JsonImplicits extends DefaultJsonProtocol {
implicit val healthFormat = jsonFormat2(Health)
}
object MyApplication {
val host = "localhost"
val port = 8080
def main(args: Array[String]): Unit = {
implicit val system = ActorSystem("simple-rest-system")
// Something to do with flows
implicit val materializer = ActorMaterializer()
// A reference to a specific thread pool
// You can configure thread pool options through it
// It is the engine that executes the actors
implicit val executionContext = system.dispatcher
val requestHandler = system.actorOf(RequestHandler.props(), "requestHandler")
//Define the route
val route : Route = {
implicit val timeout = Timeout(20 seconds)
import JsonImplicits._
import spray.httpx.SprayJsonSupport._
path("health") {
get {
onSuccess(requestHandler ? GetHealthRequest) {
case response: HealthResponse =>
complete(StatusCodes.OK, s"Everything is ${response.health.status}!")
case _ =>
complete(StatusCodes.InternalServerError)
}
}
} ~ post {
// Entity extracts the body of the POST request and then converts it into a
// Health object
entity(as[Health]) { statusReport =>
onSuccess(requestHandler ? SetStatusRequest(statusReport)) {
case response: HealthResponse =>
complete(StatusCodes.OK,s"Posted health as ${response.health.status}!")
case _ =>
complete(StatusCodes.InternalServerError)
}
}
}
}
//Start up and listen for requests
val bindingFuture = Http().bindAndHandle(route, host, port)
println(s"Waiting for requests at http://$host:$port/...\nHit RETURN to terminate")
StdIn.readLine()
//Shutdown
bindingFuture.flatMap(_.unbind())
system.terminate()
}
}

Spray Akka Json Unmarshalling

I've a problem about unmarshalling objects to Json via using spray - akka.
When i'd like to use actors that returns Future[List[Person]] , it doesn't work.
If i use dao object directly, it works.
Here are my codes:
PersonDao.scala
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
case class Person(id: Int, name: String, surname: String)
object PersonDao {
def getAll: Future[List[Person]] = Future {
List[Person](Person(1, "Bilal", "Alp"), Person(2, "Ahmet", "Alp"))
}
}
EntityServiceActor.scala
import akka.actor.Actor
import com.bilalalp.akkakafka.model.PersonDao
import com.bilalalp.akkakafka.service.ServiceOperation.FIND_ALL
object ServiceOperation {
case object FIND_ALL
}
class EntityServiceActor extends Actor {
override def receive: Receive = {
case FIND_ALL => PersonDao.getAll
}
}
ServerSupervisor.scala
import akka.actor.{Actor, ActorRefFactory}
import com.bilalalp.akkakafka.webservice.TaskWebService
import spray.routing.RejectionHandler.Default
class ServerSupervisor extends Actor with PersonWebService {
implicit val system = context.system
override def receive: Receive = runRoute(entityServiceRoutes)
override implicit def actorRefFactory: ActorRefFactory = context
}
WebServiceTrait.scala
import akka.util.Timeout
import spray.routing.HttpService
import scala.concurrent.duration._
import scala.language.postfixOps
import org.json4s.NoTypeHints
import org.json4s.native.Serialization._
trait WebServiceTrait extends HttpService {
implicit def executionContext = actorRefFactory.dispatcher
implicit val json4sFormats = formats(NoTypeHints)
implicit val timeout = Timeout(120 seconds)
}
PersonWebService.scala
trait PersonWebService extends WebServiceTrait with Json4sSupport {
val json3sFormats = DefaultFormats
val entityServiceWorker = actorRefFactory.actorOf(Props[EntityServiceActor], "entityServiceActor")
val entityServiceRoutes = {
pathPrefix("person") {
pathEndOrSingleSlash {
get {
ctx => ctx.complete((entityServiceWorker ? FIND_ALL).mapTo[Person])
}
}
}
}
}
Application.scala
import akka.actor.{ActorRef, ActorSystem, Props}
import akka.io.IO
import com.bilalalp.akkakafka.server.ServerSupervisor
import spray.can.Http
object Application extends App {
implicit val system = ActorSystem("actorSystem")
val mainHandler: ActorRef = system.actorOf(Props[ServerSupervisor])
IO(Http)! Http.Bind(mainHandler, interface = Configuration.appInterface, port = Configuration.appPort)
}
When i run this code, It gives nothing and waits for a while.
After waiting browser gives this message:
The server was not able to produce a timely response to your request.
And console output is
[ERROR] [11/22/2015 21:15:24.109]
[actorSystem-akka.actor.default-dispatcher-7]
[akka.actor.ActorSystemImpl(actorSystem)] Error during processing of
request HttpRequest(GET,http://localhost:3001/person/,List(Host:
localhost:3001, Connection: keep-alive, Cache-C ontrol: no-cache,
Pragma: no-cache, User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64)
AppleWebKit/537.36 (KHTML, like Gecko) Maxthon/4.4.6.1000
Chrome/30.0.1599.101 Safari/537.36, DNT: 1, Accept-Encoding: gzip,
deflate, Accept-Language: tr-TR),Empty,HTTP/1.1)
akka.pattern.AskTimeoutException: Ask timed out on
[Actor[akka://actorSystem/user/$a/entityServiceActor#-1810673919]]
after [120000 ms]. Sender[null] sent message of type
"com.bilalalp.akkakafka.service.ServiceOperation$FIND_ALL$".
at akka.pattern.PromiseActorRef$$anonfun$1.apply$mcV$sp(AskSupport.scala:415)
at akka.actor.Scheduler$$anon$7.run(Scheduler.scala:132)
at scala.concurrent.Future$InternalCallbackExecutor$.unbatchedExecute(Future.scala:599)
at scala.concurrent.BatchingExecutor$class.execute(BatchingExecutor.scala:109)
at scala.concurrent.Future$InternalCallbackExecutor$.execute(Future.scala:597)
If i change PersonWebService.scala to this :
trait PersonWebService extends WebServiceTrait with Json4sSupport {
val json3sFormats = DefaultFormats
val entityServiceWorker = actorRefFactory.actorOf(Props[EntityServiceActor], "entityServiceActor")
val entityServiceRoutes = {
pathPrefix("person") {
pathEndOrSingleSlash {
get (
// ctx => ctx.complete((entityServiceWorker ? FIND_ALL).mapTo[Person])
ctx => ctx.complete(PersonDao getAll)
)
}
}
}
}
It works and output is :
[{"id":1,"name":"Bilal","surname":"Alp"},{"id":2,"name":"Ahmet","surname":"Alp"}]
I'd like to use actors in spray routes. I don't know whether it is a bad practice or not because i'm newbie in akka and spray.
How can i solve this? Any ideas?
Thank you.
First of all, You can type (PersonWebService.scala):
pathEndOrSingleSlash {
get {
complete {
(entityServiceWorker ? FindAll).mapTo[List[Person]]
}
}
And as #Timothy Kim said You need to send back results using "sender ! getAll.onComplete
As I an see getAll returns Future, so in my opinion best would be to resolve it in EntityServiceActor.scala:
// import the pipe pattern (see pipeTo below):
import akka.pattern.pipe
import context.dispatcher
override def receive: Receive = {
case FindAll =>
PersonDao.getAll()
.recover({ case err => List() /* could log error here */ })
.pipeTo(sender()) // do this instead of onComplete, it's safer
in this simple case getAll Future is resolved, if everything is ok, service will get list of persons, otherwise List will be empty.
Oh and another thing PersonWebService.scala should have .mapTo[List[Person]]
You need to send a result back to sender:
case FIND_ALL =>
PersonDao.getAll.pipeTo(sender())