90

Suppose I have several futures and need to wait until either any of them fails or all of them succeed.

For example: Let there are 3 futures: f1, f2, f3.

  • If f1 succeeds and f2 fails I do not wait for f3 (and return failure to the client).

  • If f2 fails while f1 and f3 are still running I do not wait for them (and return failure)

  • If f1 succeeds and then f2 succeeds I continue waiting for f3.

How would you implement it?

Michael
  • 41,026
  • 70
  • 193
  • 341

8 Answers8

88

You could use a for-comprehension as follows instead:

val fut1 = Future{...}
val fut2 = Future{...}
val fut3 = Future{...}

val aggFut = for{
  f1Result <- fut1
  f2Result <- fut2
  f3Result <- fut3
} yield (f1Result, f2Result, f3Result)

In this example, futures 1, 2 and 3 are kicked off in parallel. Then, in the for comprehension, we wait until the results 1 and then 2 and then 3 are available. If either 1 or 2 fails, we will not wait for 3 anymore. If all 3 succeed, then the aggFut val will hold a tuple with 3 slots, corresponding to the results of the 3 futures.

Now if you need the behavior where you want to stop waiting if say fut2 fails first, things get a little trickier. In the above example, you would have to wait for fut1 to complete before realizing fut2 failed. To solve that, you could try something like this:

  val fut1 = Future{Thread.sleep(3000);1}
  val fut2 = Promise.failed(new RuntimeException("boo")).future
  val fut3 = Future{Thread.sleep(1000);3}

  def processFutures(futures:Map[Int,Future[Int]], values:List[Any], prom:Promise[List[Any]]):Future[List[Any]] = {
    val fut = if (futures.size == 1) futures.head._2
    else Future.firstCompletedOf(futures.values)

    fut onComplete{
      case Success(value) if (futures.size == 1)=> 
        prom.success(value :: values)

      case Success(value) =>
        processFutures(futures - value, value :: values, prom)

      case Failure(ex) => prom.failure(ex)
    }
    prom.future
  }

  val aggFut = processFutures(Map(1 -> fut1, 2 -> fut2, 3 -> fut3), List(), Promise[List[Any]]())
  aggFut onComplete{
    case value => println(value)
  }

Now this works correctly, but the issue comes from knowing which Future to remove from the Map when one has been successfully completed. As long as you have some way to properly correlate a result with the Future that spawned that result, then something like this works. It just recursively keeps removing completed Futures from the Map and then calling Future.firstCompletedOf on the remaining Futures until there are none left, collecting the results along the way. It's not pretty, but if you really need the behavior you are talking about, then this, or something similar could work.

cmbaxter
  • 35,283
  • 4
  • 86
  • 95
  • Thank you. What happens if `fut2` fails before `fut1` ? Will we still wait for `fut1` in that case ? If we will it's not exactly what I want. – Michael Apr 28 '13 at 07:46
  • But if 3 fails first, we still wait for 1 and 2 when we could return early. Any way of doing this without requiring sequencing the futures? – The Archetypal Paul Apr 28 '13 at 07:48
  • You can install an `onFailure` handler for `fut2` to fail fast, and a `onSuccess` on `aggFut` to handle success. A success on `aggFut` implies `fut2` did complete successfully, so you only have one of the handlers called. – pagoda_5b Apr 28 '13 at 11:48
  • I added a little more to my answer to show a possible solution for fast failing if any of the futures fails. – cmbaxter Apr 28 '13 at 11:55
  • 1
    In your first example, 1 2 and 3 do not run in parallel, then run in serial. Try it with printlines and see – bwawok Jul 21 '15 at 19:51
  • @bwawok, they certainly do. Put a Thread.sleep in the first one before the println and I bet the other two print first. – cmbaxter Jul 21 '15 at 20:06
  • @cmbaxter They certainly do not. See https://gist.github.com/brianwawok/98a231396fc87675fba3 . Now if instead of for compregension you use a Future.sequence.. they WILL compute in parallel, because sequence is using fold under the hood. – bwawok Jul 21 '15 at 20:30
  • @bwawok, there is a distinct difference in your code example and mine. In my example, the `Future`s are kicked off outside of a for comprehension and then referenced in the for comprehension. In your example, you are instantiating the `Future`s inside of the for comprehension and that will definitely run them sequentially. Change your code to create the futures outside of the for comprehension (assigning them to `vals`) and then you can extract their values in the for comprehension. If you do it this way they will indeed run in parallel. – cmbaxter Jul 22 '15 at 00:50
  • @cmbaxter that is pretty terrible, seems like the compiler should compile them down to the same code, weather you declare a future in 1 line or two – bwawok Jul 22 '15 at 12:49
  • @bwawok, remember that the for comp is basically the same as fut1 flatMap fut2 (roughly at least). If you think of it that way, because the flatMap propagates the value forward to the next step then it needs fut1 to be complete first before continuing on. If the next fut is created in the flatMap itself then it won't be created or started until the preceding step completes. – cmbaxter Jul 22 '15 at 14:42
  • @cmbaxter except at compile time you can tell if the result of fut1 is used in fut2... for example, if you ignore the result of fut1 (map as _ ), it is pretty clear it is not used – bwawok Jul 22 '15 at 16:49
  • @bwawok I considered that same thing as well but the for comprehension is what it is since its not specific to futures (it is a Monad operator). The case of non-sequential future optimization would be a bit of a special case (since I'm not sure it would be safe to assume it could optimize all Monad types this same way). Still, it's kind of like optimizing for the side-effects of a specific type (in this case, knowing that initialization of a future begins its computation). One could make a variation of the for construct that is specific to futures, it's just not in Scala core. – jm0 Feb 04 '16 at 02:34
36

You can use a promise, and send to it either the first failure, or the final completed aggregated success:

def sequenceOrBailOut[A, M[_] <: TraversableOnce[_]](in: M[Future[A]] with TraversableOnce[Future[A]])(implicit cbf: CanBuildFrom[M[Future[A]], A, M[A]], executor: ExecutionContext): Future[M[A]] = {
  val p = Promise[M[A]]()

  // the first Future to fail completes the promise
  in.foreach(_.onFailure{case i => p.tryFailure(i)})

  // if the whole sequence succeeds (i.e. no failures)
  // then the promise is completed with the aggregated success
  Future.sequence(in).foreach(p trySuccess _)

  p.future
}

Then you can Await on that resulting Future if you want to block, or just map it into something else.

The difference with for comprehension is that here you get the error of the first to fail, whereas with for comprehension you get the first error in traversal order of the input collection (even if another one failed first). For example:

val f1 = Future { Thread.sleep(1000) ; 5 / 0 }
val f2 = Future { 5 }
val f3 = Future { None.get }

Future.sequence(List(f1,f2,f3)).onFailure{case i => println(i)}
// this waits one second, then prints "java.lang.ArithmeticException: / by zero"
// the first to fail in traversal order

And:

val f1 = Future { Thread.sleep(1000) ; 5 / 0 }
val f2 = Future { 5 }
val f3 = Future { None.get }

sequenceOrBailOut(List(f1,f2,f3)).onFailure{case i => println(i)}
// this immediately prints "java.util.NoSuchElementException: None.get"
// the 'actual' first to fail (usually...)
// and it returns early (it does not wait 1 sec)
gourlaysama
  • 11,240
  • 3
  • 44
  • 51
8

Here is a solution without using actors.

import scala.util._
import scala.concurrent._
import java.util.concurrent.atomic.AtomicInteger

// Nondeterministic.
// If any failure, return it immediately, else return the final success.
def allSucceed[T](fs: Future[T]*): Future[T] = {
  val remaining = new AtomicInteger(fs.length)

  val p = promise[T]

  fs foreach {
    _ onComplete {
      case s @ Success(_) => {
        if (remaining.decrementAndGet() == 0) {
          // Arbitrarily return the final success
          p tryComplete s
        }
      }
      case f @ Failure(_) => {
        p tryComplete f
      }
    }
  }

  p.future
}
FranklinChen
  • 756
  • 7
  • 6
5

For this purpose I would use an Akka actor. Unlike the for-comprehension, it fails as soon as any of the futures fail, so it's a bit more efficient in that sense.

class ResultCombiner(futs: Future[_]*) extends Actor {

  var origSender: ActorRef = null
  var futsRemaining: Set[Future[_]] = futs.toSet

  override def receive = {
    case () =>
      origSender = sender
      for(f <- futs)
        f.onComplete(result => self ! if(result.isSuccess) f else false)
    case false =>
      origSender ! SomethingFailed
    case f: Future[_] =>
      futsRemaining -= f
      if(futsRemaining.isEmpty) origSender ! EverythingSucceeded
  }

}

sealed trait Result
case object SomethingFailed extends Result
case object EverythingSucceeded extends Result

Then, create the actor, send a message to it (so that it will know where to send its reply to) and wait for a reply.

val actor = actorSystem.actorOf(Props(new ResultCombiner(f1, f2, f3)))
try {
  val f4: Future[Result] = actor ? ()
  implicit val timeout = new Timeout(30 seconds) // or whatever
  Await.result(f4, timeout.duration).asInstanceOf[Result] match {
    case SomethingFailed => println("Oh noes!")
    case EverythingSucceeded => println("It all worked!")
  }
} finally {
  // Avoid memory leaks: destroy the actor
  actor ! PoisonPill
}
Robin Green
  • 32,079
  • 16
  • 104
  • 187
  • Looks a bit too complex for such a simple task. Do I really need an actor to just wait for futures ? Thanks anyway. – Michael Apr 27 '13 at 21:34
  • 1
    I couldn't find any suitable method in the API which can do exactly what you want, but maybe I missed something. – Robin Green Apr 27 '13 at 21:36
5

You can do this with futures alone. Here's one implementation. Note that it won't terminate execution early! In that case you need to do something more sophisticated (and probably implement the interruption yourself). But if you just don't want to keep waiting for something that isn't going to work, the key is to keep waiting for the first thing to finish, and stop when either nothing is left or you hit an exception:

import scala.annotation.tailrec
import scala.util.{Try, Success, Failure}
import scala.concurrent._
import scala.concurrent.duration.Duration
import ExecutionContext.Implicits.global

@tailrec def awaitSuccess[A](fs: Seq[Future[A]], done: Seq[A] = Seq()): 
Either[Throwable, Seq[A]] = {
  val first = Future.firstCompletedOf(fs)
  Await.ready(first, Duration.Inf).value match {
    case None => awaitSuccess(fs, done)  // Shouldn't happen!
    case Some(Failure(e)) => Left(e)
    case Some(Success(_)) =>
      val (complete, running) = fs.partition(_.isCompleted)
      val answers = complete.flatMap(_.value)
      answers.find(_.isFailure) match {
        case Some(Failure(e)) => Left(e)
        case _ =>
          if (running.length > 0) awaitSuccess(running, answers.map(_.get) ++: done)
          else Right( answers.map(_.get) ++: done )
      }
  }
}

Here's an example of it in action when everything works okay:

scala> awaitSuccess(Seq(Future{ println("Hi!") }, 
  Future{ Thread.sleep(1000); println("Fancy meeting you here!") },
  Future{ Thread.sleep(2000); println("Bye!") }
))
Hi!
Fancy meeting you here!
Bye!
res1: Either[Throwable,Seq[Unit]] = Right(List((), (), ()))

But when something goes wrong:

scala> awaitSuccess(Seq(Future{ println("Hi!") }, 
  Future{ Thread.sleep(1000); throw new Exception("boo"); () }, 
  Future{ Thread.sleep(2000); println("Bye!") }
))
Hi!
res2: Either[Throwable,Seq[Unit]] = Left(java.lang.Exception: boo)

scala> Bye!
Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
5

This question has been answered but I am posting my value class solution (value classes were added in 2.10) since there isn't one here. Please feel free to criticize.

  implicit class Sugar_PimpMyFuture[T](val self: Future[T]) extends AnyVal {
    def concurrently = ConcurrentFuture(self)
  }
  case class ConcurrentFuture[A](future: Future[A]) extends AnyVal {
    def map[B](f: Future[A] => Future[B]) : ConcurrentFuture[B] = ConcurrentFuture(f(future))
    def flatMap[B](f: Future[A] => ConcurrentFuture[B]) : ConcurrentFuture[B] = concurrentFutureFlatMap(this, f) // work around no nested class in value class
  }
  def concurrentFutureFlatMap[A,B](outer: ConcurrentFuture[A], f: Future[A] => ConcurrentFuture[B]) : ConcurrentFuture[B] = {
    val p = Promise[B]()
    val inner = f(outer.future)
    inner.future onFailure { case t => p.tryFailure(t) }
    outer.future onFailure { case t => p.tryFailure(t) }
    inner.future onSuccess { case b => p.trySuccess(b) }
    ConcurrentFuture(p.future)
  }

ConcurrentFuture is a no overhead Future wrapper that changes the default Future map/flatMap from do-this-then-that to combine-all-and-fail-if-any-fail. Usage:

def func1 : Future[Int] = Future { println("f1!");throw new RuntimeException; 1 }
def func2 : Future[String] = Future { Thread.sleep(2000);println("f2!");"f2" }
def func3 : Future[Double] = Future { Thread.sleep(2000);println("f3!");42.0 }

val f : Future[(Int,String,Double)] = {
  for {
    f1 <- func1.concurrently
    f2 <- func2.concurrently
    f3 <- func3.concurrently
  } yield for {
   v1 <- f1
   v2 <- f2
   v3 <- f3
  } yield (v1,v2,v3)
}.future
f.onFailure { case t => println("future failed $t") }

In the example above, f1,f2 and f3 will run concurrently and if any fail in any order the future of the tuple will fail immediately.

Lance Gatlin
  • 193
  • 2
  • 9
5

You might want to checkout Twitter's Future API. Notably the Future.collect method. It does exactly what you want: https://twitter.github.io/scala_school/finagle.html

The source code Future.scala is available here: https://github.com/twitter/util/blob/master/util-core/src/main/scala/com/twitter/util/Future.scala

JBakouny
  • 151
  • 2
  • 2
4

You can use this:

val l = List(1, 6, 8)

val f = l.map{
  i => future {
    println("future " +i)
    Thread.sleep(i* 1000)
    if (i == 12)
      throw new Exception("6 is not legal.")
    i
  }
}

val f1 = Future.sequence(f)

f1 onSuccess{
  case l => {
    logInfo("onSuccess")
    l.foreach(i => {

      logInfo("h : " + i)

    })
  }
}

f1 onFailure{
  case l => {
    logInfo("onFailure")
  }
aknuds1
  • 65,625
  • 67
  • 195
  • 317
igreenfield
  • 1,618
  • 19
  • 36