Akka-http exception handling (Scala) - scala

I am using Akka-hhtp (scala) to send multiple http batch requests asynchronously to an API and wondering what is the right way to handle exceptions when the response code is other than 200 OK.
Below is some pseudo-code to demonstrate my point.
/* Using For comprehension here because the server API has restriction on the amount of data we can send and the time it takes them to process each request. So they require us to send multiple mini requests instead. If one of those fails, then our entire job should fail.*/
val eventuallyResponses = for {
batches <- postBatch(payload)
} yield batches
val eventualResponses = Future.sequence(eventuallyResponses)
/* Do I need to recover here? If I don't, will the actor system terminate? */
eventualResponses.recover { case es =>
log.warn("some message")
List()
}
/* As I said I need to wait for all mini batch requests to complete. If one response is different than 200, then the entire job should fail. */
val result = Await.result(eventualResponses, 10.minutes)
actorSystem.terminate().oncomplete{
case Success(_) =>
if (result.isEmpty) =>
/* This doesn't seem to interrupt the program */
throw new RuntimeException("POST failed")
} else {
log.info("POST Successful")
}
case Failure(ex) =>
log.error("error message $ex")
throw ex
}
def postBatch(payload) = {
val responseFuture: Future[HttpResponse] = httpClient.post(payload)
responseFuture.flatMap{ res =>
res.status match {
case StatusCodes.OK => Future.successful(res)
case _ => Future.failed(new RuntimeException("error message"))
}
}
}
The above code throws exception when we receive StatusCodes different than OK. It does go through the branch of result.isEmpty true, but it doesn't seem to stop/interrupt the execution of the program. I need it to do that, as this is scheduled as an Autosys job, and I need to make the job fail if at least one of the batch requests returns different response than 200 OK.
If I don't recover and let the exception be thrown then (when we receive non 200 status code), will the Actor System be terminated properly?
Do you know of a good way to do the above?
Thanks :)

As far as I understand your question you need to throw an exception from main body if some responses haven't status 200.
def postBatch(payload: HttpRequest)(implicit system: ActorSystem, ec: ExecutionContext): Future[HttpResponse] = {
Http().singleRequest(payload).flatMap(response => response.status match {
case StatusCodes.OK => Future.successful(response)
case _ => Future.failed(new RuntimeException("error message"))
})
}
val reuests: List[HttpRequest] = List(...)
/*
You don't need for comprehension here because
val eventuallyResponses = for {
batches <- postBatch(payload)
} yield batches
is equal to
val eventuallyResponses = postBatch(payload)
For comprehension doesn't process recursive sending. If you need it you should write it yourself by flatMap on each request future.
*/
val eventualResponses: Future[List[HttpResponse]] =
Future.sequence(reuests.map(postBatch)) //also, its better to add some throttling logic here
//as far as i understand you need to wait for all responses and stop the actor system after that
Await.ready(eventualResponses, 10 minutes) //wait for all responses
Await.ready(actorSystem.terminate(), Duration.Inf) //wait for actor system termination
//because of Await.ready(eventualResponses, 10 minutes) we can match on the future value and expect that it should be completed
eventualResponses.value match {
case Some(Success(responses)) =>
log.info("All requests completed")
case Some(Failure(exception)) =>
log.error("Some request failed")
throw exception //rethrow this exception
case None =>
log.error("Requests timed out")
throw RuntimeException("Requests timed out")
}

Related

Scala Akka Actors: How to send the result of an Http response back to the sender?

I'm trying to execute the following Scala code inside an Akka Actor.
class FilteringService(implicit timeout: Timeout) extends Actor {
def receive: PartialFunction[Any, Unit] = {
case GetProfiles ⇒
val requester = sender
def getProfiles = {
var result = new Array[Profile](0)
println("[GET-PROFILES] Entered, making request")
val req = Get("http://localhost:9090/profiles")
implicit val profileFormat = jsonFormat16(Profile)
val responseFuture: Future[HttpResponse] = Http().singleRequest(req)
println("[GET-PROFILES] Entered, request sent")
responseFuture.onComplete {
case Success(response) =>
println("[RES - SUCCESS] Request returned with " + response.status)
val responseAsProfiles = Unmarshal(response.entity).to[Array[Profile]]
responseAsProfiles.onComplete {
println("[UNMARSH - SUCCESS] Unmarshaling Done!")
_.get match {
case profiles: Array[Profile] =>
println("[UNMARSH - SUCCESS] Sending Profiles message to " + sender())
requester ! profiles
println("[UNMARSH - SUCCESS] Message sent to " + sender())
case _ => println("error")
}
}
case Failure(_) =>
sys.error("something wrong")
//return Future[Array[Profile]]
}
}
println("[RECEIVE] Message GetProfiles received from " + sender().toString())
getProfiles
println("[RECEIVE] Message GetProfiles invoked")
}
When the Actor receives the message "GetProfiles":
1- it sends a request to a remote server, so the result of operation is a Future[HttpResponse]
2- in case of success it retrieves the response (a JSON array) and asks for unmarshalling the object to Array[Profile]. (It's not important the Profile model). The result of Unmarshall method is a Future[Array[Profile]]
3- In case of success, I want to send the result back to the original sender!
I managed to do this, but it's a trick because I'm saving the sender in a variable, that is visible in the scope (requester).
I know that there is the pipe pattern, so I could send the responseAsProfiles object back to the sender in theory, but the object is created inside the onComplete method of the responseFuture object (we have to wait it, of course!)
So that's all!
How could I send the result back to the sender using the pipe pattern in this case?
Thanks in advance!!!
General idea is that you compose futures using map and flatMap and try to avoid using onComplete as much as possible.
See if you can convert your code to following smaller pieces and then compose:
def getRawProfileData(): Future[HttpResponse] = {
// ... here you make http request
}
def unmarshalProfiles(response: HttpResponse): Future[List[Profile]] = {
// ... unmarshalling logic
}
def getProfiles(): Future[List[Profile]] = getRawProfileData().flatMape(unmarshalProfiles)
// now from receive block
case GetProfiles ⇒ getProfiles().pipeTo(sender())

How to handle timeout from a WS call inside an Akka Actor

I have the following actor which sends a request to a WebService:
class VigiaActor extends akka.actor.Actor {
val log = Logging(context.system, this)
context.setReceiveTimeout(5 seconds)
import VigiaActor._
def receive = {
case ObraExists(numero: String, unidadeGestora: String) =>
WS.url(baseURL + s"""/obras/exists/$unidadeGestora/$numero""").withHeaders("Authorization" -> newToken).get.pipeTo(sender)
case ReceiveTimeout =>
val e = TimeOutException("VIGIA: Receive timed out")
throw e
}
override val supervisorStrategy =
OneForOneStrategy(maxNrOfRetries = 2, withinTimeRange = 1 minute) {
case _: ArithmeticException => Resume
case _: NullPointerException => Restart
case _: IllegalArgumentException => Stop
case _: TimeOutException => Resume
case _: Exception => Restart
}
}
The call to this actor is part of a validation method that should throw an exception in case of a timeout while trying communicate to the WS:
implicit val timeout = Timeout(5 seconds)
lazy val vigiaActor : ActorRef = Akka.system.actorOf(Props[VigiaActor])
(vigiaActor ? VigiaActor.ObraExists(empenho.obra.get, empenho.unidadeGestora)).map {
case r : WSResponse =>
val exists = r.body.toBoolean
if (!exists && empenho.tipoMeta.get.equals(4)) {
erros.adicionarErro(controle.codigoArquivo, row, line, s"Nº de Obra não informado ou inválido para o Tipo de Meta 4 - Obras" , TipoErroImportacaoEnum.WARNING)
}
case _ => erros.adicionarErro(controle.codigoArquivo, row, line, s"Nº de Obra não informado ou inválido para o Tipo de Meta 4 - Obras" , TipoErroImportacaoEnum.WARNING)
}
I am new to this Actor thing, and I am trying to solve some blocking situations on the code.
The problem is I have no Idea of how to "catch" the TimeOutException on the actors call.
UPDATE
switched validation method to:
protected def validateRow(row: Int, line: String, empenho: Empenho, calendarDataEnvioArquivo: Calendar)(implicit s: Session, controle: ControleArquivo, erros:ImportacaoException): Unit = {
implicit val timeout = Timeout(5 seconds)
lazy val vigiaActor : ActorRef = Akka.system.actorOf(Props[VigiaActor])
(vigiaActor ? VigiaActor.ObraExists(empenho.obra.get, empenho.unidadeGestora)).map {
case e: TimeOutException => println("TIMOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOUT!!!!")
case r: WSResponse => {...}
}
}
and the actor ReceiveTimout part to:
case ReceiveTimeout =>
val e = TimeOutException("VIGIA: Receive timed out")
sender ! e
I am getting the following log message as I was before:
[INFO] [07/20/2017 10:28:05.738] [application-akka.actor.default-dispatcher-5] [akka://application/deadLetters] Message [model.exception.TimeOutException] from Actor[akka://application/user/$c#1834419855] to Actor[akka://application/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'.
context.setReceiveTimeout(5 seconds) triggers the sending of a ReceiveTimeout message to VigiaActor if that actor doesn't receive a message for five seconds. Akka internally sends the ReceiveTimeout to your actor, which is why in your updated code, trying to send the exception to sender doesn't do what you expect. In other words, sender in the case ReceiveTimeout => clause is not the original sender of the ObraExists message.
Setting the receive timeout in VigiaActor has nothing to do with a WS request timeout, because no message is sent to VigiaActor if the request times out. Even if a message was sent to the actor when a WS request isn't completed in five seconds, another ObraExists message could have been enqueued in the actor's mailbox in the meantime, thus failing to trigger a ReceiveTimeout.
In short, setting the actor's receive timeout is not the right mechanism to handle the WS request timeout. (With your current approach of piping the result of the get request to the sender, you could adjust the sender to handle a timeout. In fact, I'd forgo the VigiaActor altogether and simply make the WS call directly in the validateRow method, but getting rid of the actor is probably not the point of your question.)
If you must handle a WS request timeout in the actor, one way to do that is something like the following:
import scala.util.{Failure, Success}
class VigiaActor extends akka.actor.Actor {
import VigiaActor._
val log = Logging(context.system, this)
def receive = {
case ObraExists(numero: String, unidadeGestora: String) =>
val s = sender // capture the original sender
WS.url(baseURL + s"""/obras/exists/$unidadeGestora/$numero""")
.withHeaders("Authorization" -> newToken)
.withRequestTimeout(5 seconds) // set the timeout
.get
.onComplete {
case Success(resp) =>
s ! resp
case Failure(e: scala.concurrent.TimeoutException) =>
s ! TimeOutException("VIGIA: Receive timed out")
case Failure(_) =>
// do something in the case of non-timeout failures
}
}
}
I think you're over-interpreting the "Let it Crash" mentality. You only throw Exceptions inside Actors in exceptional circumstances. That is, you build your Actors to cope if something crashes unexpectedly. But if it's something normal and reasonably expected, you just treat it like any other code path.
So in your case, it has nothing to do with throw or catch -- in your ReceiveTimeout clause, just send a message back to the original sender, saying that the request failed due to a timeout, and let the sender handle it however they consider appropriate. It winds up fairly similar to your success case.

How would you "connect" many independent graphs maintaining backpressure between them?

Continuing series of questions about akka-streams I have another problem.
Variables:
Single http client flow with throttling
Multiple other flows that want to use first flow simultaneously
Goal:
Single http flow is flow that makes requests to particular API that limits number of calls to it. Otherwise it bans me. Thus it's very important to maintain rate of request regardless of how many clients in my code use it.
There are number of other flows that want to make requests to mentioned API but I'd like to have backpressure from http flow. Normally you connect whole thing to one graph and it works. But it my case I have multiple graphs.
How would you solve it ?
My attempt to solve it:
I use Source.queue for http flow so that I can queue http requests and have throttling. Problem is that Future from SourceQueue.offer fails if I exceed number of requests. Thus somehow I need to "reoffer" when previously offered event completes. Thus modified Future from SourceQueue would backpressure other graphs (inside their mapAsync) that make http requests.
Here is how I implemented it
object Main {
implicit val system = ActorSystem("root")
implicit val executor = system.dispatcher
implicit val materializer = ActorMaterializer()
private val queueHttp = Source.queue[(String, Promise[String])](2, OverflowStrategy.backpressure)
.throttle(1, FiniteDuration(1000, MILLISECONDS), 1, ThrottleMode.Shaping)
.mapAsync(4) {
case (text, promise) =>
// Simulate delay of http request
val delay = (Random.nextDouble() * 1000 / 2).toLong
Thread.sleep(delay)
Future.successful(text -> promise)
}
.toMat(Sink.foreach({
case (text, p) =>
p.success(text)
}))(Keep.left)
.run
val futureDeque = new ConcurrentLinkedDeque[Future[String]]()
def sendRequest(value: String): Future[String] = {
val p = Promise[String]()
val offerFuture = queueHttp.offer(value -> p)
def addToQueue(future: Future[String]): Future[String] = {
futureDeque.addLast(future)
future.onComplete {
case _ => futureDeque.remove(future)
}
future
}
offerFuture.flatMap {
case QueueOfferResult.Enqueued =>
addToQueue(p.future)
}.recoverWith {
case ex =>
val first = futureDeque.pollFirst()
if (first != null)
addToQueue(first.flatMap(_ => sendRequest(value)))
else
sendRequest(value)
}
}
def main(args: Array[String]) {
val allFutures = for (v <- 0 until 15)
yield {
val res = sendRequest(s"Text $v")
res.onSuccess {
case text =>
println("> " + text)
}
res
}
Future.sequence(allFutures).onComplete {
case Success(text) =>
println(s">>> TOTAL: ${text.length} [in queue: ${futureDeque.size()}]")
system.terminate()
case Failure(ex) =>
ex.printStackTrace()
system.terminate()
}
Await.result(system.whenTerminated, Duration.Inf)
}
}
Disadvantage of this solution is that I have locking on ConcurrentLinkedDeque which is probably not that bad for rate of 1 request per second but still.
How would you solve this task?
We have an open ticket (https://github.com/akka/akka/issues/19478) and some ideas for a "Hub" stage which would allow for dynamically combining streams, but I'm afraid I cannot give you any estimate for when it will be done.
So that is how we, in the Akka team, would solve the task. ;)

How to handle response timeout?

In akka-http routing I can return Future as a response that implicitly converts to ToResponseMarshaller.
Is there some way to handle timeout of this future? Or timeout of connection in route level? Or one way is to use Await() function?
Right now client can wait response forever.
complete {
val future = for {
response <- someIOFunc()
entity <- someOtherFunc()
} yield entity
future.onComplete({
case Success(result) =>
HttpResponse(entity = HttpEntity(MediaTypes.`text/xml`, result))
case Failure(result) =>
HttpResponse(entity = utils.getFault("fault"))
})
future
}
Adding a timeout to an asynchronous operation means creating a new Future that is completed either by the operation itself or by the timeout:
import akka.pattern.after
val future = ...
val futureWithTimeout = Future.firstCompletedOf(
future ::
after(1.second, system.scheduler)(Future.failed(new TimeoutException)) ::
Nil
)
The second Future could also hold a successful result that replaces the error, depending on what exactly it is that you want to model.
As a side note: the presented code sample contains dead code, registering an onComplete handler on a Future only makes sense for side-effects but you seem to want to transform the Future’s value and create an HttpEntity from it. That should be done using map and recover:
future
.map(result => HttpResponse(entity = HttpEntity(MediaTypes.`text/xml`, result)))
.recover { case ex => HttpResponse(entity = utils.getFault("fault")) }
This would then be the overall return value that is passed to the complete directive.

How to handle exception with ask pattern and supervision

How should I handle an exception thrown by the DbActor here ? I'm not sure how to handle it, should pipe the Failure case ?
class RestActor extends Actor with ActorLogging {
import context.dispatcher
val dbActor = context.actorOf(Props[DbActor])
implicit val timeout = Timeout(10 seconds)
override val supervisorStrategy: SupervisorStrategy = {
OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 10 seconds) {
case x: Exception => ???
}
}
def receive = {
case GetRequest(reqCtx, id) => {
// perform db ask
ask(dbActor, ReadCommand(reqCtx, id)).mapTo[SomeObject] onComplete {
case Success(obj) => { // some stuff }
case Failure(err) => err match {
case x: Exception => ???
}
}
}
}
}
Would be glad to get your thought, thanks in advance !
There are a couple of questions I can see here based on the questions in your code sample:
What types of things can I do when I override the default supervisor behavior in the definition of how to handle exceptions?
When using ask, what types of things can I do when I get a Failure result on the Future that I am waiting on?
Let's start with the first question first (usually a good idea). When you override the default supervisor strategy, you gain the ability to change how certain types of unhandled exceptions in the child actor are handled in regards to what to do with that failed child actor. The key word in that previous sentence is unhandled. For actors that are doing request/response, you may actually want to handle (catch) specific exceptions and return certain response types instead (or fail the upstream future, more on that later) as opposed to letting them go unhandled. When an unhandled exception happens, you basically lose the ability to respond to the sender with a description of the issue and the sender will probably then get a TimeoutException instead as their Future will never be completed. Once you figured out what you handle explicitly, then you can consider all the rest of exceptions when defining your custom supervisor strategy. Inside this block here:
OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 10 seconds) {
case x: Exception => ???
}
You get a chance to map an exception type to a failure Directive, which defines how the failure will be handled from a supervision standpoint. The options are:
Stop - Completely stop the child actor and do not send any more messages to it
Resume - Resume the failed child, not restarting it thus keeping its current internal state
Restart - Similar to resume, but in this case, the old instance is thrown away and a new instance is constructed and internal state is reset (preStart)
Escalate - Escalate up the chain to the parent of the supervisor
So let's say that given a SQLException you wanted to resume and given all others you want to restart then your code would look like this:
OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 10 seconds) {
case x: SQLException => Resume
case other => Restart
}
Now for the second question which pertains to what to do when the Future itself returns a Failure response. In this case, I guess it depends on what was supposed to happen as a result of that Future. If the rest actor itself was responsible for completing the http request (let's say that httpCtx has a complete(statusCode:Int, message:String) function on it), then you could do something like this:
ask(dbActor, ReadCommand(reqCtx, id)).mapTo[SomeObject] onComplete {
case Success(obj) => reqCtx.complete(200, "All good!")
case Failure(err:TimeoutException) => reqCtx.complete(500, "Request timed out")
case Failure(ex) => reqCtx.complete(500, ex.getMessage)
}
Now if another actor upstream was responsible for completing the http request and you needed to respond to that actor, you could do something like this:
val origin = sender
ask(dbActor, ReadCommand(reqCtx, id)).mapTo[SomeObject] onComplete {
case Success(obj) => origin ! someResponseObject
case Failure(ex) => origin ! Status.Failure(ex)
}
This approach assumes that in the success block you first want to massage the result object before responding. If you don't want to do that and you want to defer the result handling to the sender then you could just do:
val origin = sender
val fut = ask(dbActor, ReadCommand(reqCtx, id))
fut pipeTo origin
For simpler systems one may want to catch and forward all of the errors. For that I made this small function to wrap the receive method, without bothering with supervision:
import akka.actor.Actor.Receive
import akka.actor.ActorContext
/**
* Meant for wrapping the receive method with try/catch.
* A failed try will result in a reply to sender with the exception.
* #example
* def receive:Receive = honestly {
* case msg => sender ! riskyCalculation(msg)
* }
* ...
* (honestActor ? "some message") onComplete {
* case e:Throwable => ...process error
* case r:_ => ...process result
* }
* #param receive
* #return Actor.Receive
*
* #author Bijou Trouvaille
*/
def honestly(receive: =>Receive)(implicit context: ActorContext):Receive = { case msg =>
try receive(msg) catch { case error:Throwable => context.sender ! error }
}
you can then place it into a package file and import a la akka.pattern.pipe and such. Obviously, this won't deal with exceptions thrown by asynchronous code.