Question

The below (contrived) code attempts to print a by-name String parameter within a future, and return when the printing is complete.

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

class PrintValueAndWait {
  def printIt(param: => String): Unit = {
    val printingComplete = future { 
      println(param);  // why does this hang?
    }
    Await.result(printingComplete, Duration.Inf)
  }
}

object Go {
  val str = "Rabbits"

  new PrintValueAndWait().printIt(str)
}

object RunMe extends App {
  Go
}

However, when running RunMe, it simply hangs while trying to evaluate param. Changing printIt to take in its parameter by-value makes the application return as expected. Alternatively, changing printIt to simply print the value and return synchronously (in the same thread) seems to work fine also.

What's happening exactly here? Is this somehow related to the Go object not having been fully constructed yet, and so the str field not being visible yet to the thread attempting to print it? Is hanging the expected behaviour here?

I've tested with Scala 2.10.3 on both Mac OS Mavericks and Windows 7, on Java 1.7.

Was it helpful?

Solution

Your code is deadlocking on the initialization of the Go object. This is a known issue, see e.g. SI-7646 and this SO question

Objects in scala are lazily initialized and a lock is taken during this time to prevent two threads from racing to initialize the object. However, if two threads simultaneously try and initialize an object and one depends on the other to complete, there will be a circular dependency and a deadlock.

In this particular case, the initialization of the Go object can only complete once new PrintValueAndWait().printIt(str) has completed. However, when param is a by name argument, essentially a code block gets passed in which is evaluated when it is used. In this case the str argument in new PrintValueAndWait().printIt(str) is shorthand for Go.str, so when the thread the future runs on tries to evaluate param it is essentially calling Go.str. But since Go hasn't completed initialization yet, it will try to initialize the Go object too. The other thread initializing Go has a lock on its initialization, so the future thread blocks. So the first thread is waiting on the future to complete before it finishes initializing, and the future thread is waiting for the first thread to finish initializing: deadlock.

In the by value case, the string value of str is passed in directly, so the future thread doesn't try to initialize Go and there is no deadlock.

Similarly, if you leave param as by name, but change Go as follows:

object Go {
  val str = "Rabbits"

  {
    val s = str
    new PrintValueAndWait().printIt(s)
  }
}

it won't deadlock, since the already evaluated local string value s is passed in, instead of Go.str, so the future thread won't try and initialize Go.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top