1
votes

I'm working on a Finagle HTTP application where services were implemented without taking advantage of Futures and accessing Redis via a third-party lib. Such services have the following form:

class SampleOldService extends Service[Request, Response] {
  def apply(req: Request): Future[Response] = {
    val value: Int = getValueFromRedis()
    val response: Response = buildResponse(value)
    Future.value(response)
  }
}

(They are much more complex than this -- the point here is that they are synchronous.)

At some point we began developing new services with Futures and also using the Finagle Redis API. Redis calls are encapsulated in a Store class. New services have the following form:

class SampleNewService extends Service[Request, Response] {
  def apply(req: Request): Future[Response] = {
    val value: Future[Int] = Store.getValue()
    val response: Future[Response] = value map buildResponse
    response
  }
}

(They are much more complex than this -- the point here is that they are asynchronous.)

We began refactoring the old services to also take advantage of asynchronicity and Futures. We want to do this incrementally, without having to fully re-implement them at once.

The first step was to try to use the new Store class, with code like this:

class SampleOldService extends Service[Request, Response] {
  def apply(req: Request): Future[Response] = {
    val valueFuture: Future[Int] = Store.getValue()
    val value: Int = Await.result(valueFuture)
    val response: Response = buildResponse(value)
    Future.value(response)
  }
}

However, it proved to be catastrophic, because on heavy loads the requests to the old services are stuck at the Await.result() call. The new asynchronous services show no issue.

The problem seems to be related to exhaustion of thread and/or future pools. We have found several solutions on how to do synchronous calls (which perform I/O) from asynchronous calls by using custom pools (such as FuturePool), but not the other way round, which is our case.

So, what is the recommended way of calling asynchronous code (which perform I/O) from synchronous code in Finagle?

2
If apply returns Future[Response] then why do you say the code is synchronous ? - goralph
@MichaelKendra Because it is returning only after having built the Response object (via Future.value(response)), instead of computing and building it asynchronously, which is the main reason for Futures. - erdavila
@MichaelKendra Or, in other words, it is simply using Future[Response] as wrapper for a ready-made Response, instead of a handler to something that will eventually be available at some point in the "future". - erdavila

2 Answers

1
votes

The easiest thing you can do is wrap your synchronous calls with a Thread Pool that return a Future. Twitter's util-core provides the FuturePool utility to achieve exactly that.

Something like that (untested code):

import com.twitter.util.FuturePool

val future = FuturePool.unboundedPool {
  val result = myBlockingCall.await()
  result
}
1
votes

You can use FuturePool which are futures that run on top of a cached threadpool, but why do that when you can, have the service return a promise and set the value of the promise when you complete the future from the store class.

val p: Promise[Response] = Promise[Response]()
val value: Future[Int] = Store.getValue()
value onSuccess {x => 
  val result: Response = buildResponse(x)
  p.setValue(result)
}
p