Matt
Matt

Reputation: 11357

Do all futures need to be waited on to guarantee their execution?

We have a Scala Play webapp which does a number of database operations as part of a HTTP request, each of which is a Future. Usually we bubble up the Futures to an async controller action and let Play handle waiting for them.

But I've also noticed in a number of places we don't bubble up the Future or even wait for it to complete. I think this is bad because it means the HTTP request wont fail if the future fails, but does it actually even guarantee the future will be executed at all, since nothing is going to wait on the result of it? Will Play drop un-awaited futures after the HTTP request has been served, or leave them running in the background?

Upvotes: 2

Views: 196

Answers (2)

Yuval Itzchakov
Yuval Itzchakov

Reputation: 149636

does it actually even guarantee the future will be executed at all, since nothing is going to wait on the result of it? Will Play drop un-awaited futures after the HTTP request has been served, or leave them running in the background?

This question actually runs much deeper than Play. You're generally asking "If I don't synchronously wait on a future, how can I guarantee it will actually complete without being GCed?". To answer that, we need to understand how the GC actually views threads. From the GC point of view, a thread is what we call a "root". Such a root is the starting point for the heap to traverse it's objects and see which ones are eligible for collection. Among roots are also static fields, for example, which are known to live throughout the life time of the application.

So, when you view it like that, and think of what a Future actually does, which is queue a function that runs on a dedicated thread from the pool of threads available via the underlying ExecutorService (which we refer to as ExecutionContext in Scala), you see that even though you're not waiting on the completion, the JVM runtime does guarantee that your Future will run to completion. As for the Future object wrapping the function, it holds a reference to that unfinished function body so the Future itself isn't collected.

When you think about it from that point of view, it's totally logical, since execution of a Future happens asynchronously, and we usually continue processing it in an asynchronous manner using continuations such as map, flatMap, onComplete, etc.

Upvotes: 1

Simon
Simon

Reputation: 6363

TL;DR

  1. Play will not kill your Futures after sending the HTTP response.
  2. Errors will not be reported if any of your Futures fail.

Long version

Your futures will not be killed when the HTTP response has been sent. You can try that out for yourself like this:

  def futuresTest = Action.async { request =>

    println(s"Entered futuresTest at ${LocalDateTime.now}")

    val ignoredFuture = Future{
      var i = 0
      while (i < 10) {
        Thread.sleep(1000)
        println(LocalDateTime.now)
        i += 1
      }
    }

    println(s"Leaving futuresTest at ${LocalDateTime.now}")

    Future.successful(Ok)
  }

However you are right that the request will not fail if any of the futures fail. If this is a problem then you can compose the futures using a for comprehension or flatMaps. Here's an example of what you can do (I'm assuming that your Futures only perform side efects (Future[Unit])

To let your futures execute in paralell

val dbFut1 = dbCall1(...)
val dbFut2 = dbCall2(...)
val wsFut1 = wsCall1(...)
val fut = for(
    _ <- dbFut1;
    _ <- dbFut2;
    _ <- wsFut1
) yield ()

fut.map(_ => Ok)

To have them execute in sequence

val fut = for(
    _ <- dbCall1(...);
    _ <- dbCall2(...);
    _ <- wsCall2(...)
) yield ()

fut.map(_ => Ok)

Upvotes: 2

Related Questions