@@ -12,70 +12,64 @@ import kotlin.jvm.*
12
12
@SharedImmutable
13
13
private val UNDEFINED = Symbol (" UNDEFINED" )
14
14
15
- @NativeThreadLocal
16
- internal object UndispatchedEventLoop {
17
- data class EventLoop (
18
- @JvmField var isActive : Boolean = false ,
19
- @JvmField val queue : ArrayQueue <Runnable > = ArrayQueue ()
20
- )
21
-
22
- @JvmField
23
- internal val threadLocalEventLoop = CommonThreadLocal { EventLoop () }
24
-
25
- /* *
26
- * Executes given [block] as part of current event loop, updating related to block [continuation]
27
- * mode and state if continuation is not resumed immediately.
28
- * [doYield] indicates whether current continuation is yielding (to provide fast-path if event-loop is empty).
29
- * Returns `true` if execution of continuation was queued (trampolined) or `false` otherwise.
30
- */
31
- inline fun execute (continuation : DispatchedContinuation <* >, contState : Any? , mode : Int ,
32
- doYield : Boolean = false, block : () -> Unit ) : Boolean {
33
- val eventLoop = threadLocalEventLoop.get()
34
- if (eventLoop.isActive) {
35
- // If we are yielding and queue is empty, we can bail out as part of fast path
36
- if (doYield && eventLoop.queue.isEmpty) {
37
- return false
38
- }
39
-
40
- continuation._state = contState
41
- continuation.resumeMode = mode
42
- eventLoop.queue.addLast(continuation)
43
- return true
44
- }
45
-
46
- runEventLoop(eventLoop, block)
47
- return false
15
+ /* *
16
+ * Executes given [block] as part of current event loop, updating related to block [continuation]
17
+ * mode and state if continuation is not resumed immediately.
18
+ * [doYield] indicates whether current continuation is yielding (to provide fast-path if event-loop is empty).
19
+ * Returns `true` if execution of continuation was queued (trampolined) or `false` otherwise.
20
+ */
21
+ private inline fun executeUndispatched (
22
+ continuation : DispatchedContinuation <* >, contState : Any? , mode : Int ,
23
+ doYield : Boolean = false, block : () -> Unit
24
+ ) : Boolean {
25
+ val eventLoop = ThreadLocalEventLoop .eventLoop
26
+ // If we are yielding and undispatched queue is empty, we can bail out as part of fast path
27
+ if (doYield && eventLoop.isEmptyUnconfinedQueue) return false
28
+ return if (eventLoop.isUnconfinedLoopActive) {
29
+ // When undispatched loop is active -- dispatch continuation for execution to avoid stack overflow
30
+ continuation._state = contState
31
+ continuation.resumeMode = mode
32
+ eventLoop.dispatchUnconfined(continuation)
33
+ true // queued into the active loop
34
+ } else {
35
+ // Was not active -- run event loop until undispatched tasks are executed
36
+ runUndispatchedEventLoop(eventLoop, block = block)
37
+ false
48
38
}
39
+ }
49
40
50
- fun resumeUndispatched (task : DispatchedTask <* >): Boolean {
51
- val eventLoop = threadLocalEventLoop.get()
52
- if (eventLoop.isActive) {
53
- eventLoop.queue.addLast(task)
54
- return true
41
+ private fun resumeUndispatched (task : DispatchedTask <* >) {
42
+ val eventLoop = ThreadLocalEventLoop .eventLoop
43
+ if (eventLoop.isUnconfinedLoopActive) {
44
+ // When undispatched loop is active -- dispatch continuation for execution to avoid stack overflow
45
+ eventLoop.dispatchUnconfined(task)
46
+ } else {
47
+ // Was not active -- run event loop until undispatched tasks are executed
48
+ runUndispatchedEventLoop(eventLoop) {
49
+ task.resume(task.delegate, MODE_UNDISPATCHED )
55
50
}
56
-
57
- runEventLoop(eventLoop, { task.resume(task.delegate, MODE_UNDISPATCHED ) })
58
- return false
59
51
}
52
+ }
60
53
61
- inline fun runEventLoop (eventLoop : EventLoop , block : () -> Unit ) {
62
- try {
63
- eventLoop.isActive = true
64
- block()
65
- while (true ) {
66
- val nextEvent = eventLoop.queue.removeFirstOrNull() ? : return
67
- nextEvent.run ()
68
- }
69
- } catch (e: Throwable ) {
70
- /*
71
- * This exception doesn't happen normally, only if user either submitted throwing runnable
72
- * or if we have a bug in implementation. Anyway, reset state of the dispatcher to the initial.
73
- */
74
- eventLoop.queue.clear()
75
- throw DispatchException (" Unexpected exception in undispatched event loop, clearing pending tasks" , e)
76
- } finally {
77
- eventLoop.isActive = false
54
+ private inline fun runUndispatchedEventLoop (
55
+ eventLoop : EventLoop ,
56
+ block : () -> Unit
57
+ ) {
58
+ eventLoop.incrementUseCount(unconfined = true )
59
+ try {
60
+ block()
61
+ while (eventLoop.processNextEvent() <= 0 ) {
62
+ // break when all undispatched continuations where executed
63
+ if (eventLoop.isEmptyUnconfinedQueue) break
78
64
}
65
+ } catch (e: Throwable ) {
66
+ /*
67
+ * This exception doesn't happen normally, only if user either submitted throwing runnable
68
+ * or if we have a bug in implementation. Throw an exception that better explains the problem.
69
+ */
70
+ throw DispatchException (" Unexpected exception in undispatched event loop" , e)
71
+ } finally {
72
+ eventLoop.decrementUseCount(unconfined = true )
79
73
}
80
74
}
81
75
@@ -107,7 +101,7 @@ internal class DispatchedContinuation<in T>(
107
101
resumeMode = MODE_ATOMIC_DEFAULT
108
102
dispatcher.dispatch(context, this )
109
103
} else {
110
- UndispatchedEventLoop .execute (this , state, MODE_ATOMIC_DEFAULT ) {
104
+ executeUndispatched (this , state, MODE_ATOMIC_DEFAULT ) {
111
105
withCoroutineContext(this .context, countOrElement) {
112
106
continuation.resumeWith(result)
113
107
}
@@ -122,7 +116,7 @@ internal class DispatchedContinuation<in T>(
122
116
resumeMode = MODE_CANCELLABLE
123
117
dispatcher.dispatch(context, this )
124
118
} else {
125
- UndispatchedEventLoop .execute (this , value, MODE_CANCELLABLE ) {
119
+ executeUndispatched (this , value, MODE_CANCELLABLE ) {
126
120
if (! resumeCancelled()) {
127
121
resumeUndispatched(value)
128
122
}
@@ -139,7 +133,7 @@ internal class DispatchedContinuation<in T>(
139
133
resumeMode = MODE_CANCELLABLE
140
134
dispatcher.dispatch(context, this )
141
135
} else {
142
- UndispatchedEventLoop .execute (this , state, MODE_CANCELLABLE ) {
136
+ executeUndispatched (this , state, MODE_CANCELLABLE ) {
143
137
if (! resumeCancelled()) {
144
138
resumeUndispatchedWithException(exception)
145
139
}
@@ -204,9 +198,26 @@ internal fun <T> Continuation<T>.resumeDirectWithException(exception: Throwable)
204
198
else -> resumeWithException(exception)
205
199
}
206
200
201
+ private const val UNCONFINED_TASK_BIT = 1 shl 31
202
+
207
203
internal abstract class DispatchedTask <in T >(
208
- @JvmField var resumeMode : Int
204
+ resumeMode : Int
209
205
) : SchedulerTask() {
206
+ private var _resumeMode : Int = resumeMode // can have UNCONFINED_TASK_BIT set
207
+
208
+ public var resumeMode: Int
209
+ get() = _resumeMode and UNCONFINED_TASK_BIT .inv ()
210
+ set(value) { _resumeMode = value }
211
+
212
+ /* *
213
+ * Set to `true` when this task comes from [Dispatchers.Unconfined] or from another dispatcher
214
+ * that returned `false` from [CoroutineDispatcher.isDispatchNeeded],
215
+ * but there was event loop running, so it was submitted into that event loop.
216
+ */
217
+ public var isUnconfinedTask: Boolean
218
+ get() = _resumeMode and UNCONFINED_TASK_BIT != 0
219
+ set(value) { _resumeMode = if (value) resumeMode or UNCONFINED_TASK_BIT else resumeMode }
220
+
210
221
public abstract val delegate: Continuation <T >
211
222
212
223
public abstract fun takeState (): Any?
@@ -246,7 +257,7 @@ internal abstract class DispatchedTask<in T>(
246
257
}
247
258
248
259
internal fun DispatchedContinuation<Unit>.yieldUndispatched (): Boolean =
249
- UndispatchedEventLoop .execute (this , Unit , MODE_CANCELLABLE , doYield = true ) {
260
+ executeUndispatched (this , Unit , MODE_CANCELLABLE , doYield = true ) {
250
261
run ()
251
262
}
252
263
@@ -259,7 +270,7 @@ internal fun <T> DispatchedTask<T>.dispatch(mode: Int = MODE_CANCELLABLE) {
259
270
if (dispatcher.isDispatchNeeded(context)) {
260
271
dispatcher.dispatch(context, this )
261
272
} else {
262
- UndispatchedEventLoop . resumeUndispatched(this )
273
+ resumeUndispatched(this )
263
274
}
264
275
} else {
265
276
resume(delegate, mode)
0 commit comments