Why does only this specific code break when everything else works?
No problems when commenting out the for loop OR join(). Just doesn't work with both.
Using a while loop works, putting the loop in another class also works, works in the REPL or as a script without wrapping it as an object/class (using @main), works in Scala 2. Doesn't work with a loop in a function in the same class/object
object Main extends App {
val t = new Thread() {
override def run() = {
println("Started")
for (j <- 1 to 2) {println("Work")}
}
}
t.start()
t.join()
}
Edit:
By doesn't work I mean unexpected behavior: Nothing in the loop or after the loop executes. Also replacing join() with Thread.sleep(3000), the new thread (containing the for loop) continues executing (starting off at the for loop) only after the main thread finishes sleeping
It can be tested in Scastie
Iterating over any collection seems to cause the problem. For can be replaced with this block and it still won't print
it = List(1, 2, 3)
it.foreach(x => println(x))
But this will print, which shouldn't work because this is the definition of foreach
it = List(1, 2, 3)
while(it.hasNext) println(it.next())
Scala 3 no longer treats DelayedInit
the way Scala 2 did, with the result that the body of an App
now executes as part of the static initializer (as it would for any other object
). Any thread apart from the one that loads the class will block until such time as the static initializer finishes (in this case, the static initializer exits before the synthetic main
method from App
is even called).
In the case of Seq(1, 2).foreach { _ => println("Working") }
, the body of { _ => println("Working") }
is hoisted out into being part (likely a method, though I haven't inspected the bytecode) of the enclosing Main
object and thus the call to foreach
blocks until Main
is a "real object" in the eyes of the JVM, which isn't until the static initializer finishes. Unfortunately, the static initializer (thanks to the join
) is waiting for foreach
to finish. while
is still specially interpreted by the compiler and turns into a loop in the bytecode.
This can be seen by the minimal change of not extending App
and only moving the call to join
into a main method, as in this Scastie. The static initializer forks the thread and is finished (making Main
a real object in the eyes of the JVM and thus allowing the foreach
in the forked thread to execute). After this, the main method is entered and is able to join
the forked thread.