Skip to content

JS Dispatcher yieldEvery 16 configurable #3857

@mvanassche

Description

@mvanassche

The Default dispatcher in JS yields every 16 message, but it is not configurable easily.
This is an issue, because the impact in a browser can be dramatic when a large number of coroutines are manipulating the DOM,
as the browser will repaint every 16 messages.

16 is arbitrary, it is not a good or bad number as such, but since it can have a significant impact on performance, on user-experience, this should be configurable, no?

I suppose we can provide a custom Dispatcher, but the existing ones are fine, but it is internal/private.

Activity

mvanassche

mvanassche commented on Aug 22, 2023

@mvanassche
Author

Actually, what I probably need is a different kind of queue, based on time: yield back to "JS macrotask event loop" minimum after a certain duration.

mvanassche

mvanassche commented on Aug 23, 2023

@mvanassche
Author

I created a specific dispatcher that is time based, and allows to yield back to the browser after so many milliseconds (for example after 10ms, then 100ms then 500ms)

class WindowDispatcher(window: Window, yieldEveryMilliseconds: List<Int>) : CoroutineDispatcher() {
    private val queue = WindowMessageQueue(window, yieldEveryMilliseconds)

    override fun dispatch(context: CoroutineContext, block: Runnable) = queue.enqueue(block)
}

private class WindowMessageQueue(private val window: Window, yieldEveryMilliseconds: List<Int>) : MessageQueue(yieldEveryMilliseconds) {
    private val messageName = "dispatchCoroutine"

    init {
        window.addEventListener("message", { event: dynamic ->
            if (event.source == window && event.data == messageName) {
                event.stopPropagation()
                process()
            }
        }, true)
    }

    override fun schedule() {
        Promise.resolve(Unit).then { process() }
    }

    override fun reschedule() {
        window.postMessage(messageName, "*")
    }
}
// yield to JS macrotask event loop after certain duration, which can vary
internal abstract class MessageQueue(val yieldEveryMilliseconds: List<Int>) : MutableList<Runnable> by ArrayDeque() {
    var lastYield = Date.now()
    var yieldEveryIndex = -1
    private var scheduled = false

    abstract fun schedule()

    abstract fun reschedule()

    fun enqueue(element: Runnable) {
        add(element)
        if (!scheduled) {
            scheduled = true
            schedule()
        }
    }

    fun process() {
        lastYield = Date.now()
        if(yieldEveryIndex < yieldEveryMilliseconds.lastIndex) yieldEveryIndex++
        val yieldAfter = yieldEveryMilliseconds[yieldEveryIndex]
        try {
            while(Date.now() - lastYield < yieldAfter) {
                val element = removeFirstOrNull()
                if(element != null) {
                    element.run()
                } else {
                    yieldEveryIndex = -1
                    return
                }
            }
        } finally {
            if (isEmpty()) {
                yieldEveryIndex = -1
                scheduled = false
            } else {
                reschedule()
            }
        }
    }
}
qwwdfsad

qwwdfsad commented on Aug 28, 2023

@qwwdfsad
Member

For things like timings, it's indeed preferable to introduce your own dispatcher, I believe we cannot introduce a general enough mechanism that fits them all.

16 is arbitrary, it is not a good or bad number as such, but since it can have a significant impact on performance, on user-experience, this should be configurable, no?

It's indeed arbitrary. Changing the hardcoded constant is fine (e.g. based on a versatile benchmarks suite or overall ecosystem observations), but making it configurable is much harder -- because different libraries might have different opinions on what this constant should be. This path is more or less explored with plugguble RxJava default schedulers and we would like to avoid that

mvanassche

mvanassche commented on Aug 29, 2023

@mvanassche
Author

Ok, I understand, thank you.

It would be nice to expose some abstract WindowDispatcher/WindowMessageQueue such that it is easier to implement a custom MessageQueue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @qwwdfsad@mvanassche

        Issue actions

          JS Dispatcher yieldEvery 16 configurable · Issue #3857 · Kotlin/kotlinx.coroutines