How to identify / get automated hints with cyclic object initialization causing deadlocks in Scala?

111 Views Asked by At

The following code runs into future timeouts (in Scala 2.x and Dotty, -Xcheckinit or -Ycheck-init does not help here) because of cyclic object initialization. In complex projects these cycles usually are hidden very well. Is there any possiblity of getting help from the compiler or at least at runtime? How do you prevent this from happening in a multithreaded environment?

import scala.concurrent.Future
import scala.concurrent._
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global

object Base {
  val LeftElement = "Left"
  val RightElement = "Right"

  println("Base before: " + Thread.currentThread())
  val all = Set(Left, Right)
  println("Base after: " + Thread.currentThread())
}

object Left {
  println("Left before: " + Thread.currentThread())
  val basePath = Base.LeftElement
}

object Right {
  println("Right before: " + Thread.currentThread())
  val basePath = Base.RightElement
}

object Main extends App {
  val f1 = Future(Left)
  val f2 = Future(Right)
  println(Await.result(f1, 1 second))
  println(Await.result(f2, 1 second))
}
1

There are 1 best solutions below

0
On

In general, the compiler and JVM will not help you avoid this.

The best you can do to address this is delay evaluation of the cycle by, for instance:

  • using lazy val
  • using def

Note that either results in some overhead relative to a simple val. I haven't done experiments, but I'd suspect that lazy val (incurring the expense of some synchronization) is better for a case like

lazy val all = Set(Left, Right)

to limit allocations of redundant objects, and that def is better for a case like

def basePath = Base.LeftElement

since that's pretty likely to be inlined by JIT.

See also: How to diagnose or detect deadlocks in static initializers