Kotlinx.coroutines: Use Promise.resolve().then(...) to schedule coroutines by default for all platforms (including node.js) with fallback to setTimeout

Created on 12 Nov 2018  路  7Comments  路  Source: Kotlin/kotlinx.coroutines

The related micro-benchmark: https://esbench.com/bench/55d4d44e2efbca1100bf7251

Maybe it's worth to have a special implementation for Node.js using process.nextTick.

The related issue: #194

enhancement js

All 7 comments

I agree this could lead to a performance improvement (especially on Node.js) but using process.nextTick might not be a proper solution as it means that coroutines will have priority over any event of the event loop, so if too many coroutines are running, no event will be processed.

I made some micro-bench with the same library that power esbench.com and those where the results on my machine (Node.js v11):

setTimeout x 1,120,189 ops/sec 卤2.95% (75 runs sampled)
setImmediate x 1,325,972 ops/sec 卤2.55% (75 runs sampled)
process.NextTick x 2,623,686 ops/sec 卤2.08% (76 runs sampled)
Promise.resolve x 1,831,654 ops/sec 卤4.65% (67 runs sampled)

In the light of the differences in performances I think changing the Node.js dispatcher implementation is something to consider.

I uploaded the bench if you are interested bench.zip

Another reason to do it (ASAP?): #863

I am afraid we can't use Promise.resolve by default.

The problem (and the virtue) of promises lies in the fact that they use different scheduling mechanism aka microtasking. And microtasks queue has the priority over regular tasks (e.g. ready-to-run setTimeout tasks or animation tasks).

I am concerned that with microtasking as default dispatch mechanism, any user can easily shoot himself in the foot.

For example, do you expect the following snippet to eventually return null?

withTimeoutOrNull(100) {  // <- setTimeout({ thisBlock.cancel() }, 100)
    while (true) {
      yield() // <- Promise.resolve().then(thisBlock)
    }
} 

If microtasking is used for dispatching, this code may or may not complete, depending on the JS engine. E.g. in node on my local machine it hangs. In the chrome this block completes with null successfully.

Maybe having setTimeout(0) as primary dispatcher and Promise.resolve as the immediate dispatcher would work out? with this the developer could choose when he wants a faster dispatching, knowing the risks of bypassing the regular event loop.

This idea sounds good and I was thinking about it as well.
Though I see at least one problem with that: immediate dispatch will behave differently on different platforms, and moreover, on different JS engines:

println(1)
launch(Dispatchers.Main.immediate) {
  println(2)
}
println(3)

will print 1 2 3 on JVM and 1 3 2 on JS. Note that the problem is not the order of invocation, but completely different semantics.

What we can do though is to provide another JS-specific property, e.g. Dispatchers.Main.foo that uses resolve as dispatch mechanism. The only question are how to name it

Good point, the fact that different JS engines behave differently is annoying but JS devs are (unfortunately) used to this kind of things.
Using another name sounds good, though I have no idea how to name it. Maybe withResolve or promiseBased, but that looks a bit verbose.

Fixed in 1.3.0-M1

Was this page helpful?
0 / 5 - 0 ratings

Related issues

elizarov picture elizarov  路  35Comments

NikolayMetchev picture NikolayMetchev  路  46Comments

LouisCAD picture LouisCAD  路  64Comments

fvasco picture fvasco  路  70Comments

PaulWoitaschek picture PaulWoitaschek  路  47Comments