Situation:
There are a number of blocking synchronous calls (this is a given which cannot be changed) which can potentially take a long time for which the results need to be aggregated.
Goal:
Make the calls non-blocking, then wait for a max time (ms) and collect all the calls that have succeeded even though some might have failed because they have timed out (so we can degrade functionality on the failed calls).
Current solution:
The solution below works by combining the futures, wait for that one to either finish or timeout and in the case of a NonFatal error (timeout) it uses the completedFutureValues
method to extract the futures which completed successfully.
import scala.concurrent.{Await, Future}
import scala.util.Random._
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}
import scala.util.control.NonFatal
def potentialLongBlockingHelloWorld(i: Int): String = {Thread.sleep(nextInt(500)); s"hello world $i" }
// use the same method 3 times, but in reality is different methods (with different types)
val futureHelloWorld1 = Future(potentialLongBlockingHelloWorld(1))
val futureHelloWorld2 = Future(potentialLongBlockingHelloWorld(2))
val futureHelloWorld3 = Future(potentialLongBlockingHelloWorld(3))
val combinedFuture: Future[(String, String, String)] = for {
hw1 <- futureHelloWorld1
hw2 <- futureHelloWorld2
hw3 <- futureHelloWorld3
} yield (hw1, hw2, hw3)
val res = try {
Await.result(combinedFuture, 250.milliseconds)
} catch {
case NonFatal(_) => {
(
completedFutureValue(futureHelloWorld1, "fallback hello world 1"),
completedFutureValue(futureHelloWorld2, "fallback hello world 2"),
completedFutureValue(futureHelloWorld3, "fallback hello world 3")
)
}
}
def completedFutureValue[T](future: Future[T], fallback: T): T =
future.value match {
case Some(Success(value)) => value
case Some(Failure(e)) =>
fallback
case None =>
fallback
}
it will return tuple3 with either the completed future result or the fallback, for example:
(hello world,fallback hello world 2,fallback hello world 3)
Although this works, I'm not particularly happy with this.
Question:
How can we improve on this?