Skip to content

Deadlock with inner object class init #9312

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
scabug opened this issue May 14, 2015 · 3 comments
Open

Deadlock with inner object class init #9312

scabug opened this issue May 14, 2015 · 3 comments
Milestone

Comments

@scabug
Copy link

scabug commented May 14, 2015

When the following program is run, the threads end up in a deadlock.

object DeadLockTest {
  def main(args: Array[String]): Unit = {
    def run(block: => Unit): Unit =
      new Thread(new Runnable {def run(): Unit = block}).start()

    run {println(Parent.Child1)}
    run {println(Parent.Child2)}

  }

  object Parent { self =>
    trait Child {
      Thread.sleep(2000) // ensure concurrent behavior
      val parent = self
      def siblings = parent.children - this
    }

    object Child1 extends Child
    object Child2 extends Child

    final val children = Set(Child1, Child2)
  }
}

The deadlock happens because the Child classes are initialized before the Parent. This leads to the following chain of actions:

  • thread 1 starts to init class Child1 (calls the static initializer cinit, which calls the constructor init)
  • thread 2 starts to init class Child2
  • thread 1 starts to init Parent to get a reference to it into the val parent
  • thread 2 tries to get reference to Parent to place it in the val parent, but as the class Parent is being inited by thread 1, it waits
  • thread 1 tries to get reference to Child2 to place it into the val children, but as the Child2 class is being initied by thread2, it waits

For me as a programmer, when looking at the syntax, I would expect Parent.Child1 to mean that we take a reference to Parent, which has a field called Child1 containing the reference to the singleton object Child1. This would ensure that Parent1 is initialized before Child2 can be used. Ofcourse what really happens is that a reference to Parent.Child1 just uses the Parent1.MODULE$ to get the reference without bother Parent at all. Thus to me this is a bug/design failure in how scala handles inner objects.

@scabug
Copy link
Author

scabug commented May 14, 2015

Imported From: https://issues.scala-lang.org/browse/SI-9312?orig=1
Reporter: Heikki Vesalainen (hvesalai)
Affected Versions: 2.11.6
See #9428

@scabug
Copy link
Author

scabug commented Apr 11, 2016

@szeiger said:
Related to #9428

@pabloazul
Copy link

Does anyone have an opinion on if this is working as intended, or is a bug?

liufengyun added a commit to scala/scala3 that referenced this issue Jun 16, 2023
The problem is illustrated by the example below:

``` Scala
class Foo(val opposite: Foo)
case object A extends Foo(B)     // A -> B
case object B extends Foo(A)     // B -> A
```
The check aims to be simple for programmers to understand, expressive,
fast, and sound.

The check is centered around two design ideas: (1) initialization-time
irrelevance; (2) partial ordering.

The check enforces the principle of _initialization-time irrelevance_,
which means that the time when a static object is initialized should not
change program semantics. For that purpose, it enforces the following
rule:

> **The initialization of a static object should not directly or
indirectly read or write mutable state owned by another static object**.

This principle not only puts the initialization of static objects on a
solid foundation but also avoids whole-program analysis.

Partial ordering means that the initialization dependencies of static
objects form a directed-acyclic graph (DAG). No cycles with length
bigger than 1 allowed --- which might lead to deadlocks in the presence
of concurrency and strong coupling & subtle contracts between objects.

Related Issues:

#16152
#9176
#11262
scala/bug#9312
scala/bug#9115
scala/bug#9261
scala/bug#5366
scala/bug#9360
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants