• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3  */
4 
5 package kotlinx.coroutines
6 
7 import kotlinx.coroutines.internal.*
8 import org.w3c.dom.*
9 import kotlin.coroutines.*
10 import kotlin.js.Promise
11 
12 private const val MAX_DELAY = Int.MAX_VALUE.toLong()
13 
14 private fun delayToInt(timeMillis: Long): Int =
15     timeMillis.coerceIn(0, MAX_DELAY).toInt()
16 
17 internal sealed class SetTimeoutBasedDispatcher: CoroutineDispatcher(), Delay {
18     inner class ScheduledMessageQueue : MessageQueue() {
19         internal val processQueue: dynamic = { process() }
20 
21         override fun schedule() {
22             scheduleQueueProcessing()
23         }
24 
25         override fun reschedule() {
26             setTimeout(processQueue, 0)
27         }
28     }
29 
30     internal val messageQueue = ScheduledMessageQueue()
31 
32     abstract fun scheduleQueueProcessing()
33 
34     override fun dispatch(context: CoroutineContext, block: Runnable) {
35         messageQueue.enqueue(block)
36     }
37 
38     override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle {
39         val handle = setTimeout({ block.run() }, delayToInt(timeMillis))
40         return ClearTimeout(handle)
41     }
42 
43     override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
44         val handle = setTimeout({ with(continuation) { resumeUndispatched(Unit) } }, delayToInt(timeMillis))
45         // Actually on cancellation, but clearTimeout is idempotent
46         continuation.invokeOnCancellation(handler = ClearTimeout(handle).asHandler)
47     }
48 }
49 
50 internal object NodeDispatcher : SetTimeoutBasedDispatcher() {
scheduleQueueProcessingnull51     override fun scheduleQueueProcessing() {
52         process.nextTick(messageQueue.processQueue)
53     }
54 }
55 
56 internal object SetTimeoutDispatcher : SetTimeoutBasedDispatcher() {
scheduleQueueProcessingnull57     override fun scheduleQueueProcessing() {
58         setTimeout(messageQueue.processQueue, 0)
59     }
60 }
61 
62 private class ClearTimeout(private val handle: Int) : CancelHandler(), DisposableHandle {
63 
disposenull64     override fun dispose() {
65         clearTimeout(handle)
66     }
67 
invokenull68     override fun invoke(cause: Throwable?) {
69         dispose()
70     }
71 
toStringnull72     override fun toString(): String = "ClearTimeout[$handle]"
73 }
74 
75 internal class WindowDispatcher(private val window: Window) : CoroutineDispatcher(), Delay {
76     private val queue = WindowMessageQueue(window)
77 
78     override fun dispatch(context: CoroutineContext, block: Runnable) = queue.enqueue(block)
79 
80     override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
81         window.setTimeout({ with(continuation) { resumeUndispatched(Unit) } }, delayToInt(timeMillis))
82     }
83 
84     override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle {
85         val handle = window.setTimeout({ block.run() }, delayToInt(timeMillis))
86         return object : DisposableHandle {
87             override fun dispose() {
88                 window.clearTimeout(handle)
89             }
90         }
91     }
92 }
93 
94 private class WindowMessageQueue(private val window: Window) : MessageQueue() {
95     private val messageName = "dispatchCoroutine"
96 
97     init {
eventnull98         window.addEventListener("message", { event: dynamic ->
99             if (event.source == window && event.data == messageName) {
100                 event.stopPropagation()
101                 process()
102             }
103         }, true)
104     }
105 
schedulenull106     override fun schedule() {
107         Promise.resolve(Unit).then({ process() })
108     }
109 
reschedulenull110     override fun reschedule() {
111         window.postMessage(messageName, "*")
112     }
113 }
114 
115 /**
116  * An abstraction over JS scheduling mechanism that leverages micro-batching of dispatched blocks without
117  * paying the cost of JS callbacks scheduling on every dispatch.
118  *
119  * Queue uses two scheduling mechanisms:
120  * 1) [schedule] is used to schedule the initial processing of the message queue.
121  *    JS engine-specific microtask mechanism is used in order to boost performance on short runs and a dispatch batch
122  * 2) [reschedule] is used to schedule processing of the queue after yield to the JS event loop.
123  *    JS engine-specific macrotask mechanism is used not to starve animations and non-coroutines macrotasks.
124  *
125  * Yet there could be a long tail of "slow" reschedules, but it should be amortized by the queue size.
126  */
127 internal abstract class MessageQueue : ArrayQueue<Runnable>() {
128     val yieldEvery = 16 // yield to JS macrotask event loop after this many processed messages
129     private var scheduled = false
130 
schedulenull131     abstract fun schedule()
132 
133     abstract fun reschedule()
134 
135     fun enqueue(element: Runnable) {
136         addLast(element)
137         if (!scheduled) {
138             scheduled = true
139             schedule()
140         }
141     }
142 
processnull143     fun process() {
144         try {
145             // limit number of processed messages
146             repeat(yieldEvery) {
147                 val element = removeFirstOrNull() ?: return@process
148                 element.run()
149             }
150         } finally {
151             if (isEmpty) {
152                 scheduled = false
153             } else {
154                 reschedule()
155             }
156         }
157     }
158 }
159 
160 // We need to reference global setTimeout and clearTimeout so that it works on Node.JS as opposed to
161 // using them via "window" (which only works in browser)
setTimeoutnull162 private external fun setTimeout(handler: dynamic, timeout: Int = definedExternally): Int
163 private external fun clearTimeout(handle: Int = definedExternally)
164