How to run Akka Streams graph on a separate dispatcher with timeout? - scala

This question is based on a pet project that I did and this SO thread. Inside a Akka HTTP route definition, I start a long-running process, and naturally I want to do that without blocking the user. I'm able to achieve this with the code snippet below:
blocking-io-dispatcher {
type = Dispatcher
executor = "thread-pool-executor"
thread-pool-executor {
fixed-pool-size = 16
}
throughput = 1
}
complete {
Try(new URL(url)) match {
case scala.util.Success(u) => {
val src = Source.fromIterator(() => parseMovies(u).iterator)
src
.via(findMovieByTitleAndYear)
.via(persistMovies)
.toMat(Sink.fold(Future(0))((acc, elem) => Applicative[Future].map2(acc, elem)(_ + _)))(Keep.right)
// run the whole graph on a separate dispatcher
.withAttributes(ActorAttributes.dispatcher("blocking-io-dispatcher"))
.run.flatten
.onComplete {
_ match {
case scala.util.Success(n) => logger.info(s"Created $n movies")
case Failure(t) => logger.error(t, "Failed to process movies")
}
}
Accepted
}
case Failure(t) => logger.error(t, "Bad URL"); BadRequest -> "Bad URL"
}
}
What's the problem then if I've already solved it? The problem is that I'm not sure how to set a timeout. The execution of the graph creates a Future that executes until complete on the dedicated blocking-io-dispatcher. If I add a Await call, the code blocks. Is there a way to put a timeout?

completionTimeout stage should help here. Example below:
src
.completionTimeout(5.seconds)
...
.run.flatten
.onComplete {
case scala.util.Success(n) => logger.info(s"Created $n movies")
case Failure(t: TimeoutException) => logger.error(t, "Timed out")
case Failure(t) => logger.error(t, "Failed to process movies")
}
Docs reference here.

Related

Better way to setup graceful shutdown with Akka Streams

I have pretty simple application that has an Akka HTTP endpoint, does some processing and writes results to either of the output files. The code to ensure graceful shutdown looks a bit complicated, is there a way to make it more succinct?
val bindingFuture = Http().newServerAt("localhost", config.port).bind(route)
val validQueue: BoundedSourceQueue[ByteString] = ???
val invalidQueue: BoundedSourceQueue[ByteString] = ???
val validDone: Future[Done] = ???
val invalidDone: Future[Done] = ???
val allDone = Future.sequence(validDone, invalidDone)
bindingFuture.onComplete {
case Success(binding) =>
logger.info("Server started on port {}", config.port)
binding.addToCoordinatedShutdown(5.seconds)
case Failure(ex) =>
logger.error("Can't start server", ex)
system.terminate()
}
allDone.onComplete { result =>
result match {
case Failure(ex) =>
logger.error("Streams completed with error", ex)
case Success(_) =>
logger.info("Streams completed successfully")
}
system.terminate()
}
sys.addShutdownHook {
logger.info("Shutting down...")
validQueue.complete()
invalidQueue.complete()
}
Akka installs JVM shutdown hooks by default, there is no need to add a shutdown hook yourself, you can remove sys.addShutdownHook { ... }
Calling ActorSystem.terminate will also terminate all streams. (Streams can be terminated abruptly, in most applications this is not a problem though, and it sounds like this would also not be a problem in your case.)
Minor cleanup, you could consider using map, recoverWith and andThen.
allDone.map { _ =>
logger.info("Streams completed successfully")
}.recoverWith {
case ex =>
logger.error("Streams completed with error", ex)
}.andThen {
case _ => system.terminate()
}
You could use CoordinatedShutdown:
CoordinatedShutdown(context.system).addTask(CoordinatedShutdown.PhaseServiceRequestsDone, "complete hdfs sinks") { () =>
validQueue.complete()
invalidQueue.complete()
}
You could also use a shared kill switch (https://doc.akka.io/docs/akka/current/stream/stream-dynamic.html#sharedkillswitch), which you can put in your flows with .via(sharedKillSwitch.flow), you could shutdown the switch from the CoordinatedShutdown:
// create it somewhere, use in your flows
val sharedKillSwitch = KillSwitches.shared("hdfs-switch")
// use switch in CoordinatedShutdown
CoordinatedShutdown(context.system).addTask(CoordinatedShutdown.PhaseServiceRequestsDone, "complete hdfs sinks") { () =>
sharedKillSwitch.shutdown()
}

Handle Future with transform

I have a telegram bot on Scala and I want to send the image to the user if it exists and the message "Sorry, the image doesn't exist" if it's not. And I have a function getImage(tag), which returns Future.successful(link) or Future.failed(NoImageException(msg)).
onCommand("/img") { implicit msg =>
val tag = msg.text.get.drop("/img ".length)
try {
if (tag.isEmpty) throw new IndexOutOfBoundsException()
service.getImage(tag).transform {
case Success(link) => Success(
try {
replyWithPhoto(InputFile(link))
} catch {
case _ => reply(link) // maybe it isn't a photo...
})
case Failure(e) => Success(reply(e.getMessage))
}.void
} catch {
case _: IndexOutOfBoundsException => reply("Empty argument list. Usage: /img tag").void
}}
And this code sends an image if it's a success, but it doesn't send a message if it's a failure (but it definitely chooses case Failure(e) in this case)
reply family of functions return a Future[Message]. Currently you wrap the result of reply in Success, so the result of your transform is Future[Future[Message]], which doesn't work. Instead you can use transformWith, which expects a Future result from its argument:
onCommand("/img") { implicit msg =>
val tag = msg.text.get.drop("/img ".length)
val message: Future[Message] =
if (tag.isEmpty) reply("Empty argument list. Usage: /img tag")
else {
service.getImage(tag).transformWith {
case Success(link) => replyWithPhoto(InputFile(link)).recoverWith {
case _ => reply(link) // maybe it isn't a photo...
}
case Failure(e) => reply(e.getMessage)
}
}
message.void
}
Note, that I have also removed both try operators. The external is just unnecessary, because you can just use if/else. The internal won't work at all, because replyWithPhoto returns a Future. So it doesn't throw errors, and you need to recover or transform when it fails.

Akka-http exception handling (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")
}

Scala future with akka-http

I need to write simple web service with akka-http and reactivemongo.
Function to save data looks like this
def saveRoute(route: Route):Future[WriteResult] = {
collection.insert(route)
}
a code that calls this function looks like this
val userRoutes = {
logRequestResult("akka-http-microservice") {
path("routes") {
(post & entity(as[Route])) { route =>
Database.saveRoute(route)
}
}
}
}
I need to return result with inserted ID of Route and do this without making the thread to wait.
if try
Database.saveRoute(route).onComplete{
case Success(r) => complete(r.toString)
case Failure(e) => complete(e.getMessage)
}
It cannot compile, because it doesn't return value.
I know how to make it in dirty way, but really want to make in appropriate manner.
What should be done in this case?
Seems like I've found most efficient way to do this. It's built in onComplete directive
(path("routes" / "add") & post & entity(as[Route])) {
route =>
onComplete(routesController.addRoute(route)) {
case Success(result) => complete(StatusCodes.Created, "OK")
case Failure(ex) => complete(new ErrorResponse(StatusCodes.InternalServerError.intValue, ErrorResponse.ERROR, ex.getMessage))
}
}
Use onSuccess to handle the valid response when the future finishes and handleExceptions to handle when the future does not succeed.
val userRoutes = {
handleExceptions(mongoDbExceptionHandler) {
logRequestResult("akka-http-microservice") {
path("routes") {
(post & entity(as[Route])) { route =>
onSuccess(Database.saveRoute(route)) { result =>
complete(result)
}
}
}
}
}
}
// Something like this for whatever the exceptions you expect are
val mongoDbExceptionHandler = ExceptionHandler {
case ex: MongoDbReadException => complete(HttpResponse(InternalServerError, "No database")))
}
onSuccess:
http://doc.akka.io/docs/akka/2.4.9/scala/http/routing-dsl/directives/future-directives/onSuccess.html
handleExceptions:
http://doc.akka.io/docs/akka/2.4.9/scala/http/routing-dsl/exception-handling.html
You can map over the future and then complete the request like below.
val future = Database.saveRoute(route)
val response = future.map(_.getId).recover(_.getMessage)
complete(response)
On a side note, for handling exceptions, it is a good practice to have a ExceptionHandler and wrap it with your route. You can find example here.
You have few option i will try to put the most commonly used ones for REST API based solutions:
OnSuccess use it when you want your expectations to be bubbled and handled by expectionHandler
concat(
path("success") {
onSuccess(Future { "Ok" }) { extraction =>
complete(extraction)
}
},
path("failure") {
onSuccess(Future.failed[String](TestException)) { extraction =>
complete(extraction)
}
}
)
https://doc.akka.io/docs/akka-http/current/routing-dsl/directives/future-directives/onSuccess.html
onComplete: When you want to manually handle the exception. Try Monad wrapped.
val route =
path("divide" / IntNumber / IntNumber) { (a, b) =>
onComplete(divide(a, b)) {
case Success(value) => complete(s"The result was $value")
case Failure(ex) => complete((InternalServerError, s"An error occurred: ${ex.getMessage}"))
}
}
https://doc.akka.io/docs/akka-http/current/routing-dsl/directives/future-directives/onComplete.html
How about this, replace:
Database.saveRoute(route)
with:
complete(Database.saveRoute(route).map(_.toString).recover(_.getMessage))
When you use RequestContext you should use something like this:
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.RouteResult.{Complete, Rejected}
...
val myRoute: Route = (path("my-path") & get) { req: RequestContext =>
val futureResp: Future[HttpResponse] = ???
futureResp.map(resp => RouteResult.Complete(resp))
}

Understanding Spray's detach directive

I'm trying to understand Spray's detach directive and whether there's any difference between using detach and using the onComplete directive. What I want to achieve is "one thread per request" where by thread I mean JVM thread ( which should be an OS thread).
So, what the difference between A and B ?
Method A:
// assume controller.divide(a,b) returns Future[Int]
val route =
path("divide" / IntNumber / IntNumber) { (a, b) =>
onComplete(controller.divide(a, b)) {
case Success(value) => complete(s"The result was $value")
case Failure(ex) => complete(InternalServerError, s"An error occurred: ${ex.getMessage}")
}
}
Method B:
// assume controller.divide(a,b) returns Int
val route = {
path("divide" / IntNumber / IntNumber) { (a,b) =>
detach(global) {
Try { controller.divide(a,b) } match {
case Success(value) => complete(s"The result was $value")
case Failure(ex) => complete(InternalServerError, s"An error occurred: ${ex.getMessage}")
}
}
} }
Cheers !
The point of detach is that you don't need your method to return a Future - that's really it.
The idea is to run items you know won't throw exceptions compactly, but without needing to block the handler thread. You should rewrite Method B as:
detach(global) {
complete(s"The result was ${controller.divide(a, b)}")
}
In the spray-routing world, the Failure case would be handled by an outer handleExceptions directive.
See Spray's `detach` Directive where the accepted answer explains very well, what
detach does and
how it uncouples from the route executing actor.