scalafuturetwitter-finagletwitter-util

Twitter Futures - how are unused futures handled?


So I have a api in Scala that uses twitter.util.Future.

In my case, I want to create 2 futures, one of which is dependent on the result of the other and return the first future:

def apiFunc(): Future[Response]={
  val future1 = getFuture1()
  val future2 = future1 map {
     resp => processFuture1Resp(resp)
  } 

  future1
}

So in this case, future2 is never consumed and the api returns the result of future1 before future2 is completed.

My question is - will future2 run even though the api has returned?

Further, future1 should not be affected by future2. That is, the processing time for future2 should not be seen by any client that calls apiFunc(). You can think of processFuture1Resp as sending the results of getFuture1() to another service but the client calling apiFunc() doesn't care about that portion and only wants future1 as quickly as possible

My understanding is that futures spawn threads, but I am unsure if that thread will be terminated after the return of the main thread.

I guess a different way to ask this is - Will a twitter.util.future always be executed? Is there a way to fire and forget a twitter.util.future?


Solution

  • If you want to chain two futures where one of them processes the result of the other (and return a future), you can use a for comprehension:

      def getFuture1() = {
        println("Getting future 1...")
        Thread.sleep(1000)
        Future.successful("42")
      }
    
      def processFuture1Resp(resp: String) = {
        println(s"Result of future 1 is: ${resp}")
        "END"
      }
    
      def apiFunc(): Future[String]={
        for {
          res1 <- getFuture1()
          res2 <- Future(processFuture1Resp(res1))
        } yield res2
      }
    
      def main(args: Array[String]) {
        val finalResultOfFutures: Future[String] = apiFunc()
        finalResultOfFutures
        Thread.sleep(500)
      }
    

    This will print:

    Getting future 1...    
    Result of future 1 is: 42
    

    The value finalResultOfFutures will contain the result of chaining both futures, and you'll be sure that the first future is executed before the second one. If you don't wait for the execution of finalResultOfFutures on the main thread (commenting the last sleep function of the main thread), you will only see:

    Getting future 1...
    

    The main thread will finish before the second future has time to print anything.

    Another (better) way to wait for the execution of the future would be something like this:

    val maxWaitTime: FiniteDuration = Duration(5, TimeUnit.SECONDS)
    Await.result(finalResultOfFutures, maxWaitTime)
    

    Await.result will block the main thread and waits a defined duration for the result of the given Future. If it is not ready or completes with a failure, Await.result will throw an exception.

    EDITED

    Another option is to use Monix Tasks This library allows you wrap actions (such as Futures) and have a greater control on how and when the Futures are executed. Since a Future may start its execution right after its declaration, these functionalities can be quite handy. Example:

      import monix.execution.Scheduler.Implicits.global
      import scala.concurrent.duration._
    
      def getFuture1() = {
        println("Getting future 1...")
        Thread.sleep(3000)
        println("Future 1 finished")
        Future.successful("42")
      }
    
      def processFuture1Resp(resp: Task[String]) = {
        val resp1 = resp.runSyncUnsafe(2.seconds)
        println(s"Future2: Result of future 1 is: ${resp1}")
      }
      
      def main(args: Array[String]) {
        // Get a Task from future 1. A Task does not start its execution until "run" is called
        val future1: Task[String] = Task.fromFuture(getFuture1())
        // Here we can create the Future 2 that depends on Future 1 and execute it async. It will finish even though the main method ends.
        val future2 = Task(processFuture1Resp(future1)).runAsyncAndForget
        // Now the future 1 starts to be calculated. Yo can runAsyncAndForget here or return just the task so that the client
        // can execute it later on
        future1.runAsyncAndForget
      }
    

    This will print:

    Getting future 1...
    Future 1 finished
    Future2: Result of future 1 is: 42
    
    Process finished with exit code 0