I have this little program that I expect to print “hi” a couple times and then exit. It works with Scala 2.13, but with Scala 3, it prints once and then hangs. I’m guessing this is some significant whitespace thing I don’t understand. Does anyone see the problem?
import java.util.concurrent.{CountDownLatch, Executors, TimeUnit}
object MainTest extends App {
val scheduler = Executors.newSingleThreadScheduledExecutor()
val c = new CountDownLatch(2)
val r: Runnable = () => {
println("hi")
c.countDown()
}
scheduler.scheduleAtFixedRate(r, 1L, 1L, TimeUnit.SECONDS)
c.await()
scheduler.shutdown()
}
It’s becouse trait App used DelayedInit which was deprecated and removed in Scala 3 - Dropped: DelayedInit
To fix the issue you can an explicit def main(args: Array[String]): Unit method wrapping your logic if it needs to cross-compile with both Scala 2 and Scala 3 or to use @main if you’re going to use only Scala 3 https://docs.scala-lang.org/scala3/reference/changed-features/main-functions.html
2 Likes
Thank you, @WojciechMazur . That worked. I hope that App is removed soon so no one else has this problem.
Here is a program that works. The problem isn’t Scala’s scheduler at all but the fact that you’ve put your “run-and-await” logic in the object’s initialization rather than inside main. It leads to two problems.
- When you launch the JVM it loads
ScheduledRuns and immediately runs all of your top-level vals and expressions (that includes creating the scheduler, calling scheduleAtFixedRate, and then blocking on c.await()), before it ever calls your main method.
- You initialized your
CountDownLatch to 2, which means you have to see two his before c.await() will return. The first scheduled execution fires after one second, prints hi (count goes from 2→1), but then you’re still waiting for the second tick (at 2 s), so the JVM sits there…and you never get to the main body.
You’ll see exactly one hi after one second and then nothing, because you never gave it your full two counts, and you’ve blocked the startup before main ever ran.
object ScheduledRuns {
import java.util.concurrent.{CountDownLatch, Executors, TimeUnit}
def main(args: Array[String]): Unit = {
val scheduler = Executors.newSingleThreadScheduledExecutor()
// if you only want one “hi” before you quit, use CountDownLatch(1)
val c = new CountDownLatch(2)
val r: Runnable = () => {
println("hi")
c.countDown()
}
// first run at 1 s, then every 1 s thereafter
scheduler.scheduleAtFixedRate(r, 1L, 1L, TimeUnit.SECONDS)
// block here until we’ve seen two “hi”s
c.await()
// clean up the executor and then exit
scheduler.shutdown()
println("Done.")
}
}