Closed
Description
First, some table-setting:
var canceled = false;
const loop = () => {
if (!canceled) {
console.log("looping");
new Promise((resolve, reject) => loop());
}
};
setTimeout(() => { console.log("canceling"); canceled = true; }, 5);
loop();
This does what you would expect: it loops for a while and then cancels.
So does the following:
import scala.concurrent.ExecutionContext
import scala.concurrent.duration._
import scala.scalajs.concurrent.QueueExecutionContext
import scala.scalajs.js.timers
object Test {
def main(args: Array[String]): Unit = {
var cancel = false
val exec: ExecutionContext = QueueExecutionContext.timeouts()
def loop(): Unit = {
if (!cancel) {
println("looping")
exec.execute(() => loop())
}
}
timers.setTimeout(5.millis) { println("canceling"); cancel = true }
loop()
}
}
Again, loops for a little while and then cancels.
This, however, loops forever:
import scala.concurrent.ExecutionContext
import scala.concurrent.duration._
import scala.scalajs.concurrent.QueueExecutionContext
import scala.scalajs.js.timers
object Test {
def main(args: Array[String]): Unit = {
var cancel = false
val exec: ExecutionContext = QueueExecutionContext.promises() // <---- this is the only difference!
def loop(): Unit = {
if (!cancel) {
println("looping")
exec.execute(() => loop())
}
}
timers.setTimeout(5.millis) { println("canceling"); cancel = true }
loop()
}
}
It appears that if any outstanding work is available on the promises
queue, it will dominate over the timeouts queue. My guess without looking at the code is that this is a consequence of a well-intentioned optimization within ScalaJS itself which fails to yield, since it definitely doesn't reproduce within Node.