Following the Akka Cluster documentation, I have the Worker Dial-in example running.
http://doc.akka.io/docs/akka/snapshot/java/cluster-usage.html
So I've trying to integrate that with a spray routing.
My idea is to have a cluster behind the scenes and through a http rest, call that service.
So I have the following code.
object Boot extends App {
val port = if (args.isEmpty) "0" else args(0)
val config =
ConfigFactory
.parseString(s"akka.remote.netty.tcp.port=$port")
.withFallback(ConfigFactory.parseString("akka.cluster.roles = [frontend]"))
.withFallback(ConfigFactory.load())
val system = ActorSystem("ClusterSystem", config)
val frontend = system.actorOf(Props[TransformationFrontend], name = "frontend")
implicit val actSystem = ActorSystem()
IO(Http) ! Http.Bind(frontend, interface = config.getString("http.interface"), port = config.getInt("http.port"))
}
class TransformationFrontend extends Actor {
var backends = IndexedSeq.empty[ActorRef]
var jobCounter = 0
implicit val timeout = Timeout(5 seconds)
override def receive: Receive = {
case _: Http.Connected => sender ! Http.Register(self)
case HttpRequest(GET, Uri.Path("/job"), _, _, _) =>
jobCounter += 1
val backend = backends(jobCounter % backends.size)
val originalSender = sender()
val future : Future[TransformationResult] = (backend ? new TransformationJob(jobCounter + "-job")).mapTo[TransformationResult]
future onComplete {
case Success(s) =>
println("received from backend: " + s.text)
originalSender ! s.text
case Failure(f) => println("error found: " + f.getMessage)
}
case job: TransformationJob if backends.isEmpty =>
sender() ! JobFailed("Service unavailable, try again later", job)
case job: TransformationJob =>
jobCounter += 1
backends(jobCounter % backends.size) forward job
case BackendRegistration if !backends.contains(sender()) =>
println("backend registered")
context watch sender()
backends = backends :+ sender()
case Terminated(a) =>
backends = backends.filterNot(_ == a)
}
}
But what I really want to do is to combining the spray routing with those pattern matching.
Instead of writing my GET like the above, I would like to write like this:
path("job") {
get {
respondWithMediaType(`application/json`) {
complete {
(backend ? new TransformationJob(jobCounter + "-job")).mapTo[TransformationResult]
}
}
}
}
But extending my Actor with this class, I have to do the following
def receive = runRoute(defaultRoute)
How can I combine this approach with my TransformationFrontend Actor pattern matching methods? BackendRegistration, Terminated, TransformationJob?
You can compose PartialFunctions like Receive with PartialFunction.orElse:
class TransformationFrontend extends Actor {
// ...
def myReceive: Receive = {
case job: TransformationJob => // ...
// ...
}
def defaultRoute: Route =
get {
// ...
}
override def receive: Receive = runRoute(defaultRoute) orElse myReceive
}
That said, it often makes sense to split up functionality into several actors (as suggested in the comment above) if possible.
Related
I am currently trying to build an akka-http websocket connection that can:
broadcast to all connected clients
answer to certain requests of the clients
This is how I create my flow so far:
// keeps a list of all actors so I can broadcast to them
var actors: List[ActorRef] = Nil
private def wsFlow(implicit materializer: ActorMaterializer): Flow[ws.Message, ws.Message, NotUsed] = {
val (actor, source) = Source.actorRef[String](10, akka.stream.OverflowStrategy.dropTail)
.toMat(BroadcastHub.sink[String])(Keep.both)
.run()
// this never triggers
source.watchTermination() { (m, f) =>
f.onComplete(r => println("TERMINATION: " + r.toString))
actors = actors diff actor :: Nil
m
}
actors = actor :: actors
val wsHandler: Flow[ws.Message, ws.Message, NotUsed] =
Flow[ws.Message]
.merge(source)
.map {
case TextMessage.Strict(tm) => handleMessage(actor, tm)
case _ => TextMessage.Strict("Ignored message!")
}
wsHandler
}
def broadcast(msg: String): Unit = {
actors.foreach(_ ! TextMessage.Strict(msg))
}
The - hopefully - last problem I am encountering is that the watchTermination callback never triggers (I am never getting a "TERMINATION: ..." message on my console). Why is that? And how is it possible to detect when a client is leaving (so I can remove him from my actors list)?
I figured out how to do it:
val wsHandler: Flow[ws.Message, ws.Message, NotUsed] = Flow[ws.Message]
.watchTermination() { (m, f) =>
f.onComplete(r => {
println("Client left: " + r.toString)
actors = actors diff actor :: Nil
}
)
m
}
.merge(source)
.map {
case TextMessage.Strict(tm) => handleMessage(actor, tm)
case _ => TextMessage.Strict("Ignored message!")
}
I need to create variable number of actors each time my program starts and then must ensure all responses are return after a period of time. This
link gives a good idea for fixed number of actors but what about dynamic number?
This is my code that creates actor and passes messages to them:
ruleList = ...
val childActorList: Iterable[ActorRef] = ruleList.map(ruleItem =>
context.actorOf(DbActor.props(ruleItem.parameter1, ruleItem.parameter2)))
implicit val timeout = Timeout(10.second)
childActorList.foreach(childActor =>
childActor ? (tempTableName, lastDate)
)
Updated-1
According to #Raman Mishra guides , I updated my code as bellow, this is the code in parent actor:
override val supervisorStrategy: SupervisorStrategy = {
OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 10 seconds) {
case exp: SQLException => //Resume;
throw exp
case exp:AskTimeoutException => throw exp
case other: Exception => throw other
}
}
override def receive: Receive = {
case Start(tempTableName, lastDate) => {
implicit val timeout = Timeout(10.second)
ruleList.foreach { ruleItem =>
val childActor = context.actorOf(DbActor.props(ruleItem._1, query = ruleItem._2))
ask(childActor, (tempTableName, lastDate)).mapTo[Seq[Int]]
onComplete {
lastDate)).mapTo[Seq[Int]] onComplete {
case util.Success(res) => println("done" + res + ruleItem._2)
case util.Failure(exp: AskTimeoutException) => println("Failed query:" + ruleItem._2); throw exp
case other => println(other)
}
}
And in child actor:
case (brokerTableName, lastDate) => {
Logger("Started query by actor" + self.path.name + ':' +
val repo = new Db()
val res = repo.getAggResult(query = (brokerTableName, lastDate))
val resWrapper = res match {
case elem: Future[Any] => elem
case elem:Any => Future(elem)
}
resWrapper pipeTo self
}
case res:List[Map[Any, Any]] => {
// here final result is send to parent actor
repo.insertAggresults(res, aggTableName) pipeTo context.parent
}
Now, whenever I run main app, first, parent actor starts and create child actors and send messages to them using ask method. Child actors do their tasks but the problem here is child actors response never returns back to parent actor and in every run of app, AskTimeoutException occurs. I doubt if the use of onComplete method is correct or not. Any help will be appreciated.
"Updated-2"
I found out the problem is in using context.parent instead of sender(). Also, when I pipe to sender, first part of my result, and the sender ask for second part, the problem is resolved but I don't know what happens here, why Can't I pipe to self and return the final result to parent?
This is the last code:
In parent actor:
override def receive: Receive = {
case Start(tempTableName, lastDate) => {
println("started: called by remote actor")
implicit val timeout = Timeout(5 second)
ruleList.foreach { ruleItem =>
val childActor = context.actorOf(DbActor.props(ruleItem._1, query = ruleItem._2))
ask(childActor, Broker(tempTableName, lastDate)) onComplete {
// (childActor ? Broker(tempTableName, lastDate)).mapTo[Seq[Int]] onComplete {
case util.Success(res: List[Map[Any, Any]]) => (childActor ? res) onComplete {
case util.Success(res: Seq[Any]) => println("Successfull- Num,ber of documents:" + res.length + " " + ruleItem._2)
case util.Failure(exp: AskTimeoutException) => println("Failed for writing - query:" + ruleItem._2); throw exp
}
case util.Failure(exp: AskTimeoutException) => println("Failed for reading - query :" + ruleItem._2); throw exp
case other => println(other)
}
}
}
}
In child actor:
case (brokerTableName, lastDate) => {
Logger("Started query by actor" + self.path.name + ':' +
val repo = new Db()
val res = repo.getAggResult(query = (brokerTableName, lastDate))
val resWrapper = res match {
case elem: Future[Any] => elem
case elem:Any => Future(elem)
}
resWrapper pipeTo sender()
}
case res:List[Map[Any, Any]] => {
// here final result is send to parent actor
repo.insertAggresults(res, aggTableName) pipeTo sender()
}
The reason that replying to sender() works where replying to context.parent does not is that ask creates an temporary actor to handle the response. You need to reply to this temporary actor: the sender (which is different from the parent).
Also it's not clear whether the getAggResult method is blocking. If so this will not help (see here).
I am looking to expand the following code to work for an unknown amount of actor ask requests.
implicit val timeout = Timeout(100 millis)
val sendRequestActor = context.actorOf(Props(new SendRequest(request)), "Send_Request_".concat(getActorNumber))
val sendRequestActor2 = context.actorOf(Props(new SendRequest(request)), "Send_Request_".concat(getActorNumber))
val a1 = ask(sendRequestActor, Request).fallbackTo(Future.successful(RequestTimeout))
val a2 = ask(sendRequestActor2, Request).fallbackTo(Future.successful(RequestTimeout))
val result = for {
r1 <- a1
r2 <- a2
} yield(r1, r2)
val r = Await.result(result, 100 millis)
r match {
case (b: SuccessResponse, b2: SuccessResponse) => {
//Process Results
}
case (b: SuccessResponse, b2: RequestTimeout) => {
//Process Results
}
case (b: RequestTimeout, b2: SuccessResponse) => {
//Process Results
}
case (b: RequestTimeout, b2: RequestTimeout) => {
//Process Results
}
case _ => {}
}
I am trying to send out requests to a List of recipients(gotten from a previous database call). The number of recipients will vary each time this function is called. Recipients have a maximum of 100 milliseconds to respond before I want to time out their requests and record a RequestTimeout. The SendRequest actor will reply with SuccessResponse if the recipients respond. I am assuming I will have to change the val result for-loop to process a list, but I am unsure of how to structure everything so that I will wait the minimum amount of time(either when all actors return or when the timeout hits, whichever is lower). I do not need everything in a single return value like the example, I am fine with a list of results and matching type on each iteration.
Any help would be appreciated, please let me know if I can provide any other information.
Thank you
Edit:
Calling Class:
case object GetResponses
def main(args: Array[String]) {
val route = {
get {
complete {
//stuff
val req_list = List(req1,req2,req3)
val createRequestActor = system.actorOf(Props(new SendAll(req_list)), "Get_Response_Actor_" + getActorNumber)
val request_future = ask(createRequestActor, GetResponses).mapTo[List[Any]]
Thread.sleep(1000)
println(request_future)
//more stuff
}
}
}
Http().bindAndHandle(route, "localhost", 8080)
}
Updated Sending Class:
class SendAll(requests: List[Request]) extends Actor {
import context.{become,dispatcher}
var numProcessed = 0
var results: List[Any] = List()
requests.foreach(self ! _)
implicit val timeout = Timeout(100 millis)
def receive = {
case r: RequestMsg =>
val sendRequestActor = context.actorOf(Props(new SendRequest(r)), "Send_Request_".concat(getActorNumber))
(sendRequestActor ? Request).pipeTo(self)
case s: SuccessResponse =>
println("Got Success")
results = results :+ s
println(results.size + " == " + requests.size)
if(results.size == requests.size) {
println("Before done")
become(done)
}
case akka.actor.Status.Failure(f) =>
println("Got Failed")
results = results :+ RequestTimeout
if(results.size == requests.size) {
become(done)
}
case m =>
println("Got Other")
}
def done: Receive = {
case GetResponses =>
println("Done")
sender ! results
case _ => {
println("Done as well")
}
}
}
Output
Got Success
1 == 3
Got Success
2 == 3
Got Success
3 == 3
Before done
Future(<not completed>)
I would pass the list of requests to the actor, then pipe the responses from the child actors to self instead of using Await.result. For example:
class Handler(requests: List[RequestMsg]) extends Actor {
import context.{become, dispatcher}
var numProcessed = 0
var results: List[Any] = List()
requests.foreach(self ! _)
implicit val timeout = Timeout(100.millis)
def receive = {
case r: RequestMsg =>
val sendRequestActor = context.actorOf(Props(new SendRequest(r)), "Send_Request".concat(getActorNumber))
(sendRequestActor ? Request).pipeTo(self)
case s: SuccessResponse =>
println(s"response: $s")
results = results :+ s
if (results.size == requests.size)
become(done)
case akka.actor.Status.Failure(f) =>
println("a request failed or timed out")
results = results :+ RequestTimeout
if (results.size == requests.size)
become(done)
case m =>
println(s"Unhandled message received while processing requests: $m")
sender ! NotDone
}
def done: Receive = {
case GetResponses =>
println("sending responses")
sender ! results
}
}
You would instantiate an actor for every list of requests:
val requests1 = List(RequestMsg("one"), RequestMsg("two"), RequestMsg("three"))
val handler1 = system.actorOf(Props(new Handler(requests1)))
In this example--following the principle that an actor should have a distinct, limited sphere of responsibility--the actor simply coordinates requests and responses; it doesn't perform any processing on the collected responses. The idea is that another actor would send this actor a GetResponses messages in order to get the responses and process them (or this actor would proactively send the results to a processing actor).
The simplest solution is put all your actor refs into the List map it to List[Future] and use Future.sequence to obtain Future[List].
val route = {
get {
val listActorRefs = List(actorRef1, actorRef2, ...)
val futureListResponses = Future.sequence(listActorRefs.map(_ ? Request))
onComplete(futureListResponses) {
case Success(listResponse) => ...
complete(...)
case Failure(exception) => ...
}
}
}
A better solution is avoid a lot of actor' asks, prepare some ResponseCollector actor which will send all your message (I suggest to look at BroadcastPool) and schedule one message for itself to stop waiting and return result.
In my below test, I tried to simulate a timeout and then send a normal request. however, I got spray.can.Http$ConnectionException: Premature connection close (the server doesn't appear to support request pipelining)
class SprayCanTest extends ModuleTestKit("/SprayCanTest.conf") with FlatSpecLike with Matchers {
import system.dispatcher
var app = Actor.noSender
protected override def beforeAll(): Unit = {
super.beforeAll()
app = system.actorOf(Props(new MockServer))
}
override protected def afterAll(): Unit = {
system.stop(app)
super.afterAll()
}
"response time out" should "work" in {
val setup = Http.HostConnectorSetup("localhost", 9101, false)
connect(setup).onComplete {
case Success(conn) => {
conn ! HttpRequest(HttpMethods.GET, "/timeout")
}
}
expectMsgPF() {
case Status.Failure(t) =>
t shouldBe a[RequestTimeoutException]
}
}
"normal http response" should "work" in {
//Thread.sleep(5000)
val setup = Http.HostConnectorSetup("localhost", 9101, false)
connect(setup).onComplete {
case Success(conn) => {
conn ! HttpRequest(HttpMethods.GET, "/hello")
}
}
expectMsgPF() {
case HttpResponse(status, entity, _, _) =>
status should be(StatusCodes.OK)
entity should be(HttpEntity("Helloworld"))
}
}
def connect(setup: HostConnectorSetup)(implicit system: ActorSystem) = {
// for the actor 'asks'
import system.dispatcher
implicit val timeout: Timeout = Timeout(1 second)
(IO(Http) ? setup) map {
case Http.HostConnectorInfo(connector, _) => connector
}
}
class MockServer extends Actor {
//implicit val timeout: Timeout = 1.second
implicit val system = context.system
// Register connection service
IO(Http) ! Http.Bind(self, interface = "localhost", port = 9101)
def receive: Actor.Receive = {
case _: Http.Connected => sender ! Http.Register(self)
case HttpRequest(GET, Uri.Path("/timeout"), _, _, _) => {
Thread.sleep(3000)
sender ! HttpResponse(entity = HttpEntity("ok"))
}
case HttpRequest(GET, Uri.Path("/hello"), _, _, _) => {
sender ! HttpResponse(entity = HttpEntity("Helloworld"))
}
}
}
}
and My config for test:
spray {
can {
client {
response-chunk-aggregation-limit = 0
connecting-timeout = 1s
request-timeout = 1s
}
host-connector {
max-retries = 0
}
}
}
I found that in both cases, the "conn" object is the same.
So I guess when RequestTimeoutException happens, spray put back the conn to the pool (by default 4?) and the next case will use the same conn but at this time, this conn is keep alive, so the server will treat it as chunked request.
If I put some sleep in the second case, it will just passed.
So I guess I must close the conn when got RequestTimeoutException and make sure the second case use a fresh new connection, right?
How should I do? Any configurations?
Thanks
Leon
You should not block inside an Actor (your MockServer). When it is blocked, it is unable to respond to any messages. You can wrap the Thread.sleep and response inside a Future. Or even better: use the Akka Scheduler. Be sure to assign the sender to a val because it may change when you respond to the request asynchronously. This should do the trick:
val savedSender = sender()
context.system.scheduler.scheduleOnce(3 seconds){
savedSender ! HttpResponse(entity = HttpEntity("ok"))
}
The code is:
// pilot = Await.result(context.actorSelection(s"../$pilotName").resolveOne, 5.seconds)
pilot = context.actorFor("../" + pilotName)
actorFor works fine in application and test.
It's really weird that the code commented works in application but fails with akka.actor.ActorNotFound(the actor it looked for existed and I think the timeout is enough) when running test.
The test is:
class PilotsSpec extends TestKit(ActorSystem("PilotsSpec",
ConfigFactory.parseString(PilotsSpec.configStr)))
with ImplicitSender with WordSpecLike with MustMatchers {
import PilotsSpec._
import plane.Plane._
def nilActor: ActorRef = TestProbe().ref
val pilotPath = s"/user/TestPilots/$pilotName"
val copilotPath = s"/user/TestPilots/$copilotName"
def pilotsReadyToGo(): ActorRef = {
implicit val timeout = Timeout(5.seconds)
val a = system.actorOf(Props(
new IsolatedStopSupervisor with OneForOneStrategyFactory {
def childrenStart() = {
context.actorOf(Props[FakePilot], pilotName)
context.actorOf(Props(new CoPilot(testActor, nilActor, nilActor)), copilotName)
}
}), "TestPilots")
Await.result(a ? IsolatedLifeCycleSupervisor.WaitForStart, 5.seconds)
system.actorFor(copilotPath) ! Pilots.ReadyToGo
a
}
"CoPilot" should {
"takecontrol when the Pilotdies" in {
pilotsReadyToGo()
// Kill the Pilot
system.actorFor(pilotPath) ! PoisonPill
// Sincethe test classis the "Plane" we can
// expect to see this request
expectMsg(GiveMeControl)
// The girl who sent it had better be Mary
lastSender must be (system.actorFor(copilotPath))
}
}
}
I don't know if I do something wrong with ActorSelection.
I try to use onComplete but it still doesn't work and throws ActorNotFound exception in test.
val f = context.actorSelection("../" + pilotName).resolveOne
f onComplete {
case Success(v) => { pilot = v; context.watch(pilot); println("pilot get") }
case Failure(e) => throw e
}
Is there anyone who knows the reason why actorFor works but actorSelection fails(in test exactly)?
Then I add the code below to the test code:
system.actorSelection(pilotPath).resolveOne map {v => println("------pilot:"+v)}
system.actorSelection(copilotPath).resolveOne map {v => println("------copilot:"+v)}
Thread.sleep(1000)
It works and prints these actorRef
Then I try to replace ../+pilotName with constant string pilotPath but it fails again(no matter context.actorSelection or context.system.actorSelection)
Here is the exception(a fragment):
test-only cc.akka.avionics.PilotsSpec
[info] Compiling 1 Scala source to G:\scala_workspace\akka_test_u7\target\scala-2.10\test-classes...
[warn] there were 2 deprecation warning(s); re-run with -deprecation for details
[warn] one warning found
------copilot:Actor[akka://PilotsSpec/user/TestPilots/Mary#962346268]
------pilot:Actor[akka://PilotsSpec/user/TestPilots/Mark#-320295209]
ActorSelection[Anchor(akka://PilotsSpec/user/TestPilots/Mary#962346268), Path(/../Mark)]
[ERROR] [04/29/2014 15:13:16.080] [PilotsSpec-akka.actor.default-dispatcher-4] [akka.dispatch.Dispatcher] Actor not found for: ActorSelection[Ancho
r(akka://PilotsSpec/user/TestPilots/Mary#962346268), Path(/../Mark)]
akka.actor.ActorNotFound: Actor not found for: ActorSelection[Anchor(akka://PilotsSpec/user/TestPilots/Mary#962346268), Path(/../Mark)]
at akka.actor.ActorSelection$$anonfun$resolveOne$1.apply(ActorSelection.scala:65)
at akka.actor.ActorSelection$$anonfun$resolveOne$1.apply(ActorSelection.scala:63)
at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:32)
at akka.dispatch.BatchingExecutor$Batch$$anonfun$run$1.processBatch$1(BatchingExecutor.scala:67)
at akka.dispatch.BatchingExecutor$Batch$$anonfun$run$1.apply$mcV$sp(BatchingExecutor.scala:82)
at akka.dispatch.BatchingExecutor$Batch$$anonfun$run$1.apply(BatchingExecutor.scala:59)
at akka.dispatch.BatchingExecutor$Batch$$anonfun$run$1.apply(BatchingExecutor.scala:59)
at scala.concurrent.BlockContext$.withBlockContext(BlockContext.scala:72)
at akka.dispatch.BatchingExecutor$Batch.run(BatchingExecutor.scala:58)
at akka.dispatch.ExecutionContexts$sameThreadExecutionContext$.unbatchedExecute(Future.scala:74)
at akka.dispatch.BatchingExecutor$class.execute(BatchingExecutor.scala:110)
Only actorFor works in test the other codes commented using actorSelection works in application but fails in test(so strange):
class CoPilot(plane: ActorRef,
var controls: ActorRef,
altimeter: ActorRef) extends Actor {
implicit val timeout = Timeout(1.second)
implicit val ct = context.dispatcher
var pilot: ActorRef = context.system.deadLetters
val pilotName: String = context.system.settings.config.getString("cc.akka.avionics.flightcrew.pilotName")
val pilotId : Int = 200
def receive = {
case ReadyToGo =>
// fails in test
// pilot = Await.result(context.actorSelection(s"../$pilotName").resolveOne, 3.seconds)
// println("get pilot:" + pilot.path + " dead:" + pilot.isTerminated)
// actorFor works
// pilot = context.actorFor("../" + pilotName)
// context.watch(pilot)
// autopilot = Await.result(context.actorSelection("../AutoPilot").resolveOne, 100.millis)
// fails in test
// val f = context.actorSelection("../" + pilotName).resolveOne
// f onComplete {
// case Success(v) => { pilot = v; context.watch(pilot); println("pilot get") }
// case Failure(e) => throw e
// }
println("-----------"+pilotName)
// fails in test
context.actorSelection("../" + pilotName) ! Identify(pilotId)
case Terminated(_) =>
plane ! GiveMeControl
case Controls(controlSurfaces) =>
controls = controlSurfaces
case ActorIdentity(pilotId, Some(ref)) =>
pilot = ref
context.watch(pilot)
println("find copilot:"+pilot)
case ActorIdentity(pilotId, None) =>
println("failed to find pilot")
}
}
actorFor creates new Actors whereas actorSelection finds actors that already exist.
I suspect the reason that your actorSelection isn't working and your actorFor does work is because the actor isn't currently in existence.