Possible initialization bug to do with package objects - causes "deadlock" at runtime

55 views
Skip to first unread message

oxbow_lakes

unread,
Jul 10, 2013, 7:19:25 AM7/10/13
to scala...@googlegroups.com
I thought I'd ask here first if people think this is a bug in scala/scalac. It's a "deadlock" observed at runtime with what I think is perfectly valid scala code - I've some sample code here to reproduce it (all using 2.10.2)

I have the following....

in package a.b.c

File Oxbow.scala

package a.b.c
trait Oxbow {
  val Bippy = a.b.c.d.Bippy //I never reference Bippy in the test but this val seems critical. Remove it and the deadlock disappears

  def systemInt = 1

  def threadName = Thread.currentThread().getName
}

object Oxbow extends Oxbow

File package.scala

package a.b

package object c extends Oxbow

In package a.b.c.d

File Bippy.scala

package a.b.c
package d
object Bippy {
  private[this] val lakes = {
    Thread.sleep(500) //the sleep is important in revealing the deadlock
    systemInt //this call is important. Remove it and the deadlock disappears
  }
}

Now I seem to be able to create some kind of "deadlock" by running a test declared at the bottom. The deadlock is caused by separate threads which are accessing a.b.c.Oxbow$class.$init$ - the first because of a call to a method brought into scope via an "import a.b.c.Oxbow._" and the second by "import a.b.c._". If you run the test below a few times, eventually you'll see the "deadlock" - I'm left with two threads which are doing this:

 

"ForkJoinPool-1-worker-9" daemon prio=6 tid=0x0000000014630800 nid=0x1940 in Object.wait() [0x000000001543e000]

   java.lang.Thread.State: RUNNABLE

                at a.b.c.Oxbow$class.$init$(Oxbow.scala:4)

                at a.b.c.package$.<init>(package.scala:3)

                at a.b.c.package$.<clinit>(package.scala)

                at test.Test1$$anonfun$bar$1$$anonfun$apply$1.apply$mcV$sp(Test1.scala:12)

                at test.Test1$$anonfun$bar$1$$anonfun$apply$1.apply(Test1.scala:9)

                at test.Test1$$anonfun$bar$1$$anonfun$apply$1.apply(Test1.scala:9)

                at scala.concurrent.impl.Future$PromiseCompletingRunnable.liftedTree1$1(Future.scala:24)

                at scala.concurrent.impl.Future$PromiseCompletingRunnable.run(Future.scala:24)

                at scala.concurrent.impl.ExecutionContextImpl$$anon$3.exec(ExecutionContextImpl.scala:107)

                at scala.concurrent.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)

                at scala.concurrent.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)

                at scala.concurrent.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)

                at scala.concurrent.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)

 

"ForkJoinPool-1-worker-11" daemon prio=6 tid=0x000000001462c000 nid=0x2a1c in Object.wait() [0x0000000014fcd000]

   java.lang.Thread.State: RUNNABLE

                at a.b.c.d.Bippy$.<init>(Bippy.scala:8)

                at a.b.c.d.Bippy$.<clinit>(Bippy.scala)

                at a.b.c.Oxbow$class.$init$(Oxbow.scala:4)

                at a.b.c.Oxbow$.<init>(Oxbow.scala:13)

                at a.b.c.Oxbow$.<clinit>(Oxbow.scala)

                at test.Test2$$anonfun$foo$1$$anonfun$apply$1.apply$mcV$sp(Test2.scala:13)

                at test.Test2$$anonfun$foo$1$$anonfun$apply$1.apply(Test2.scala:10)

                at test.Test2$$anonfun$foo$1$$anonfun$apply$1.apply(Test2.scala:10)

                at scala.concurrent.impl.Future$PromiseCompletingRunnable.liftedTree1$1(Future.scala:24)

                at scala.concurrent.impl.Future$PromiseCompletingRunnable.run(Future.scala:24)

                at scala.concurrent.impl.ExecutionContextImpl$$anon$3.exec(ExecutionContextImpl.scala:107)

                at scala.concurrent.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)

                at scala.concurrent.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)

                at scala.concurrent.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)

                at scala.concurrent.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)



I'm calling it a "deadlock" because according to JConsole, there is no deadlock and according to the stacks both threads are in RUNNABLE state and are not WAITING at all (could they have been woken up by a call to notify?) - but they don't appear to be doing anything. You can leave it for 10 minutes and the threads will be in the same state

If I change the code so that both Test1 and Test2 do "import a.b.c._" then the issue is not observed

Should I file this, or am I doing something stupid?

Chris

Here's the test code to reveal the "deadlock"

in package test

File Main.scala

package test

import scala.concurrent.Future
import java.util.concurrent.CountDownLatch

object Main extends App {

  object Test1 {
    import a.b.c._
    def bar() = (0 until 100000) map { i =>
      import scala.concurrent.ExecutionContext.Implicits.global
      Future {
        val s = i.toString + "1"
        if (i % 100 == 0) {
          println(s + " from " + threadName)
        }
      }
    }

  }

object Test2 {
  import a.b.c.Oxbow._ //notice I get threadName here differently. if this is changed to "import a.b.c._" the deadlock disappears

  def foo() = (0 until 100000) map { i =>
    import scala.concurrent.ExecutionContext.Implicits.global
    Future {
      val s = i.toString + "2"
      if (i % 100 == 0) {
        println(s + " from " + threadName)
      }
    }
  }
}

  import scala.concurrent.ExecutionContext.Implicits.global
  val l = new CountDownLatch(2)
  //Note I seem to need to declare these two threads, rather than just sequence the two seqs of futures to reveal the deadlock
  new Thread(new Runnable {
    def run() {Future.sequence(Test1.bar()) onComplete { case _ => l.countDown()}}
  }).start()
  new Thread(new Runnable {
    def run() {Future.sequence(Test2.foo()) onComplete { case _ => l.countDown()}}
  }).start()

  l.await()
}

Chris Twiner

unread,
Jul 11, 2013, 11:30:32 AM7/11/13
to oxbow_lakes, scala-user

You are best off looking at the byte code to figure object deadlocks out coupled with the byte code location.

Been bitten by similar package object problems before, never fun to figure out. Still guess is that the val is initialized in Bippy but also by D, two threads both hit lazy loading and depend on the other to fit. The locations in the byte code would reveal where. But you already know how to work around it :)

--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 
Reply all
Reply to author
Forward
0 new messages