Search code examples
scalaintellij-ideafutureblocking

Scala Future blocking with multiple Await.result() calls


I don't understand why this example, taken from here, is blocking

import java.util.concurrent.Executors
import scala.concurrent._
import scala.concurrent.duration.Duration

object Main {
  def main(args: Array[String]) {
    println("Start")
    implicit val ec = ExecutionContext.fromExecutor(Executors.newFixedThreadPool(1))
    println("Made ec")
    def addOne(x: Int) = {
      println("Executing addOne")
      Future(x)
    }

    def multiply(x: Int, y: Int): Future[Int] = Future {
      val a = addOne(x)
      val b = addOne(y)
      val result: Future[Int] = for (r1 <- a; r2 <- b) yield r1 * r2
      println("Got result")
      val awaitedResult: Int = Await.result(result, Duration.Inf)
      println("awaitedResult = " + awaitedResult)
      awaitedResult
    }
    val mult: Future[Int] = multiply(2,2)
    val multResult: Int = Await.result(mult, Duration.Inf)
    println("Result of mult = " + multResult)
  }
}

Output of IntelliJ Scala Project:

Connected to the target VM, address: '127.0.0.1:36346', transport: 'socket'
Start
Made ec
Executing addOne
Executing addOne
Got result
awaitedResult = 4
Result of mult = 4
Disconnected from the target VM, address: '127.0.0.1:36346', transport: 'socket'

Process finished with exit code 130

Note:

    Hot Swap failed Main: hierarchy change not implemented;
    Main: Operation not supported by VM

The weird thing is that in the IntelliJ console, I see the answer, 4, but I have to click the red "stop" button because it won't terminate.


Solution

  • There are two things at play here:

    1. You don't close your Executors.newFixedThreadPool(1) thread pool. Call shutdownNow() on it at the end, otherwise the non-daemon threads block Java exit.

    2. The second thing is that you need more than one thread in your thread pool because the single thread is blocked by multiple Await calls.

    Why you need more than one thread: If you add a debug message before waiting for multiplication

    val mult: Future[Int] = multiply(2,2)
    println("before multiplication")
    val multResult: Int = Await.result(mult, Duration.Inf)
    

    You will see that the output may be

    Made ec
    before multiplication result
    Executing addOne
    ...
    

    As you can see, call to multiply(2,2) just constructs a Future containing a prescription of your computation but doesn't necessarily execute anything.

    Here is how you can hit a deadlock: First, Await.result(mult, Duration.Inf) triggers the multiply() method and blocks the main thread. Body of multiply() is executed on the single thread pool thread. Then we hit Await.result(result, Duration.Inf). This again triggers computation of the result value and blocks the thread pool thread. And that's where we hit a deadlock: thread pool is blocked by waiting for Await.result(result, Duration.Inf) but there's no other thread where result value can be computed.

    How to do with single thread: Generally, you should await your futures only at the latest possible point where you need the result. That's why Scala makes it more difficult to await compared to Java. So the correct approach is to avoid await in multiply():

    def multiply(x: Int, y: Int): Future[Int] = {
      val a = addOne(x)
      val b = addOne(y)
      val result: Future[Int] = for (r1 <- a; r2 <- b) yield r1 * r2
      println("Got result")
      result.andThen{ case Success(i) => println("awaitedResult = " +i) }
    }
    

    The article you link also talks about blocking { ... }. This may help if you use the standard execution context, but not with a custom thread pool, as explained here.