The problems of Future

I was told that a problem of Future is such that if i await for the result and it times out. The future actually still runs in the background somewhere since Future is not cancellable.

I am trying to prove to myself this theory

object Main extends App {

  import concurrent.duration._

  // single threaded execution context
  implicit val context = ExecutionContext.fromExecutor(Executors.newSingleThreadExecutor())

  val f = Future {
    def infinite(n: Int): Int = {
      infinite(n + 1)
    }
    //infinite(0)
    1
  }

  println(Await.result(f, 3.seconds))
  println("Finished Await")

}

I expect that if i run infinite in the future. The main thread will timeout and the Future continues running. That’s what happens, true but if i return the future instantly the main thread still blocks ??? Can someone point out to me what i am doing wrong?

1 Like

The reason is that threads in thread pools are by default non-daemon threads (so they block exiting from VM) and you don’t explicitly shut them down.

There are some solutions.

  1. Not recommended solution - you can make a thread pool out of daemon threads. They won’t block exiting from VM:
  // don't use this solution!!!
  implicit val context: ExecutionContextExecutor = {
    val defaultThreadFactory = Executors.defaultThreadFactory()
    val threadFactory: ThreadFactory = runnable => {
      val thread = defaultThreadFactory.newThread(runnable)
      thread.setDaemon(true)
      thread
    }
    val executorService = Executors.newSingleThreadExecutor(threadFactory)
    ExecutionContext.fromExecutor(executorService)
  }
  1. You can explicitly shutdown thread pool (that works only if you don’t have running infinite jobs):
  implicit val context: ExecutionContextExecutorService = {
    val executorService = Executors.newSingleThreadExecutor()
    ExecutionContext.fromExecutorService(executorService)
  }

  val f = Future { 1 }

  try {
    println(Await.result(f, 3.seconds))
  } finally {
    println("Finished Await")
    context.shutdown()
  }
  1. Also not generally recommended (i.e. useful, but rather not as something scattered all over your codebase): invoke System.exit - that will exit no matter what:
  implicit val context: ExecutionContextExecutorService = {
    val executorService = Executors.newSingleThreadExecutor()
    ExecutionContext.fromExecutorService(executorService)
  }

  val f = Future { 1 }

  try {
    println(Await.result(f, 3.seconds))
  } finally {
    println("Finished Await")
    System.exit(0)
  }
1 Like

FWIW, if I run your code as it is, it prints 1 followed by “Finished Await” and then keeps running until it prints: “timed out after 30 seconds when running code”

if I get hold of the executor from which you obtain a context, and tell the executor to shutdown, then the program prints 1 followed by “Finished Await” and then completes.

object Main extends App {

  import java.util.concurrent.{Executors, ExecutorService}
  import concurrent.duration._
  import scala.concurrent.ExecutionContext
  import scala.concurrent.{Future,Await}
  

  val executor = Executors.newSingleThreadExecutor()
  // single threaded execution context
  implicit val context = ExecutionContext.fromExecutor(executor)

  val f = Future {
    def infinite(n: Int): Int = {
      infinite(n + 1)
    }
    //infinite(0)
    1
  }
  println("requesting executor shutdown")
  executor.shutdown
  println(Await.result(f, 3.seconds))
  println("Finished Await")
}

Thank you for the suggestions. It seems like the theory is true. The future still runs even when Await has timed out.

I realised that i have such poor understanding of Java concurrency. Is Scala Future using java concurrency libraries under the hood? How about libraries like cats-effect or ZIO or Monix? Do they have their own implementation of concurrency from the ground up?

If i want to understand more about all this should i pick up a book on Java concurrency? Because if libraries like cats-effect or ZIO or Monix have their own implementation from the ground up then what i will learn from Java concurrency might not be so relevant.

object Main extends App {

  import concurrent.duration._

  // single threaded execution context
  implicit val context: ExecutionContextExecutorService = {
    val executorService = Executors.newSingleThreadExecutor()
    ExecutionContext.fromExecutorService(executorService)
  }

  val f = Future {
    Thread.sleep(10.seconds.toMillis)
    println("Finish sleeping")
  }

  try {
    println(Await.result(f, 3.seconds))
  } catch {
    case _: Exception => println("Caught exception")
  }
  finally
   {
    println("Finished Await")
    context.shutdown()
  }

  println("At main")
}

Output

Caught exception
Finished Await
At main
//after 10 seconds
Finish sleeping

Awaiting on a Future doesn’t affect that Future at all. You can await a Future many times. For example:

import scala.concurrent._
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.Try

object Main {
  def main(args: Array[String]): Unit = {
    val ftr = Future { Thread.sleep(3.seconds.toMillis) }

    println(Try(Await.result(ftr, 1.second)))
    println(Try(Await.result(ftr, 1.second)))
    println(Try(Await.result(ftr, 1.second)))
    println(Try(Await.result(ftr, 1.second)))
    println(Try(Await.result(ftr, 1.second)))
  }
}

prints:

Failure(java.util.concurrent.TimeoutException: Future timed out after [1 second])
Failure(java.util.concurrent.TimeoutException: Future timed out after [1 second])
Success(())
Success(())
Success(())

(BTW: I’ve found a bug in scastie: https://github.com/scalacenter/scastie/issues/467 )

Scala’s Future doesn’t exhibit any means to interrupt its execution from outside. IIRC that’s by design. You have to code it yourself.

ZIO seems to handle things differently:

They all use at least Java Threads under the hood (plus Executors, but I don’t know all the details). It depends on what you mean by “from the ground up”, but in general when you run code on Java platform you want to use some Java standard library functions in the end. What would be the alternative? Coding your own standard library and replacing the Java’s one? Well, that doesn’t make much sense.

3 Likes

Yes i believe ultimately they all will use java threads under the hood. I was just thinking do they use java’s implementation of the thread pool or do they have their own implementation and custom strategies. At which level of abstraction can we say its Java and from there on its Scala

Well it is true at the end all those technologies will be running on JVM threads, they provide sufficient abstraction so you do not need to worry about that (expect for configuring your thread pools which is really important).
For example things like fibers, async and concurrent combinators, etc.

So, it really depends what you mean with learning Java concurrency.

Let’s unpack this a little.

Java concurrency per se is only semi-relevant – this isn’t Java. JVM concurrency, though, is enormously relevant, though: the JVM is the platform that both Java and Scala (usually) run on. So it’s worth at least basically understanding the JVM’s concept of threads, and that tends to be explained in terms of Java.

That said – Scala Futures are a much higher-level construct than JVM threads, so don’t make yourself crazy learning the Java side of it, because much of it is irrelevant. It might be worth reading the first few chapters of a Java book – the explanations of what threads are and how they work – but don’t waste time on most of it. (I suspect you could find some perfectly adequate articles online, but I haven’t really looked around for this.)

Also, note that much of this involved the underlying hardware: threading is really a pretty low-level construct, usually with hardware support.

The FP libraries – cats-effect, Monix and ZIO – all use a yet higher-level construct, typically called fibres. They aren’t building concurrency from the ground up, but they apply their own abstraction on top of that low-level concurrency. Deep under the hood there are still threads there, but you don’t interact with them directly nearly as much in the FP world: the majority of your code can ignore threads except when external constraints require you to deal with them. The FP engine itself will manage the relationship between your code and the threads to run it on.

3 Likes

Well explained. Thank you

lectures 5.7 to 5.16 of ‘Principles of Reactive Programming’ might be of interest: https://www.youtube.com/watch?v=fy_QYnoq9XQ&list=PLMhMDErmC1TdBMxd3KnRfYiBV2ELvLyxN

2 Likes