Description
Our mutual initialization of ir.Names
and ir.Types
can cause a deadlock if both start concurrently from two different threads. Here is the relevant portion of a thread dump:
"pool-152-thread-1" #357 prio=5 os_prio=0 cpu=28.62ms elapsed=11.37s tid=0x00007fa9dd0fa590 nid=0x45b88 in Object.wait() [0x00007fa846ffb000]
java.lang.Thread.State: RUNNABLE
at org.scalajs.ir.Types$.<init>(Types.scala:443)
- waiting on the Class initialization monitor for org.scalajs.ir.Names$
at org.scalajs.ir.Types$.<clinit>(Types.scala)
at org.scalajs.ir.TestIRBuilder$.<init>(TestIRBuilder.scala:73)
at org.scalajs.ir.TestIRBuilder$.<clinit>(TestIRBuilder.scala)
at org.scalajs.ir.HashersTest.<init>(HashersTest.scala:48)
...
"pool-152-thread-4" #361 prio=5 os_prio=0 cpu=45.37ms elapsed=11.37s tid=0x00007fa9dd340a00 nid=0x45b8d in Object.wait() [0x00007fa8487fb000]
java.lang.Thread.State: RUNNABLE
at org.scalajs.ir.Names$MethodName$.constructor(Names.scala:508)
- waiting on the Class initialization monitor for org.scalajs.ir.Types$
at org.scalajs.ir.Names$.<init>(Names.scala:656)
at org.scalajs.ir.Names$.<clinit>(Names.scala)
at org.scalajs.ir.Names$ClassName$.apply(Names.scala:550)
at org.scalajs.ir.Names$ClassName$.apply(Names.scala:553)
at org.scalajs.ir.Serializers$.<init>(Serializers.scala:54)
at org.scalajs.ir.Serializers$.<clinit>(Serializers.scala)
at org.scalajs.ir.Serializers$Hacks.<init>(Serializers.scala:2587)
at org.scalajs.ir.SerializersTest.testHacksUseBelow(SerializersTest.scala:22)
...
I had occasionally noticed ir2_12/test
to be stuck, but I had always attributed that to an sbt glitch. Fortunately (in a sense), adding new tests for Names
in #5003 turned the likelihood of this happening from "very rare" to "very often". That let me diagnose the issue properly.
The problem is that we have an inter-dependency between the constructor of object Names
and that of object Types
.
For "Names
depends on Types
": in the constructor of object Names
we have
scala-js/ir/shared/src/main/scala/org/scalajs/ir/Names.scala
Lines 653 to 666 in cf05126
which requires the
Types.XRef
s of primitive types. Those are val
s in object Types
, so we need to initialize object Types
(this wouldn't happen if they were object
s, but they are val
s because they are instances of a case class
).
For "Types
depends on Names
": in the constructor of object Types
we have
scala-js/ir/shared/src/main/scala/org/scalajs/ir/Types.scala
Lines 383 to 397 in cf05126
which similarly accesses
val
s defined in object Names
.
This is not an issue in sequential initialization, because the interdependencies only require val
s defined before the cycle starts anew. So whether we start from Names
or from Types
, we get all the things initialized in the right order. But in a concurrent scenario, the global initialization locks on JVM class initializers (rightly) create a deadlock.
We need to break this cycle somehow. My best idea so far is to move all those val
s to a third object WellKnownNames
, that neither Types
nor Names
is allowed to depend on.