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
.