Skip to content

Commit f2ec36a

Browse files
committed
Implement yield for unconfined dispatchers
Fixes #737
1 parent 43d8d98 commit f2ec36a

File tree

4 files changed

+63
-6
lines changed

4 files changed

+63
-6
lines changed

common/kotlinx-coroutines-core-common/src/Dispatched.kt

+17-4
Original file line numberDiff line numberDiff line change
@@ -21,26 +21,33 @@ internal object UndispatchedEventLoop {
2121
@JvmField
2222
internal val threadLocalEventLoop = CommonThreadLocal { EventLoop() }
2323

24-
inline fun execute(continuation: DispatchedContinuation<*>, contState: Any?, mode: Int, block: () -> Unit) {
24+
inline fun execute(continuation: DispatchedContinuation<*>, contState: Any?, mode: Int, doYield: Boolean = false, block: () -> Unit) : Boolean {
2525
val eventLoop = threadLocalEventLoop.get()
2626
if (eventLoop.isActive) {
27+
// If we are yielding and queue is empty, yield should be a no-op
28+
if (doYield && eventLoop.queue.isEmpty) {
29+
return false
30+
}
31+
2732
continuation._state = contState
2833
continuation.resumeMode = mode
2934
eventLoop.queue.addLast(continuation)
30-
return
35+
return true
3136
}
3237

3338
runEventLoop(eventLoop, block)
39+
return false
3440
}
3541

36-
fun resumeUndispatched(task: DispatchedTask<*>) {
42+
fun resumeUndispatched(task: DispatchedTask<*>): Boolean {
3743
val eventLoop = threadLocalEventLoop.get()
3844
if (eventLoop.isActive) {
3945
eventLoop.queue.addLast(task)
40-
return
46+
return true
4147
}
4248

4349
runEventLoop(eventLoop, { task.resume(task.delegate, MODE_UNDISPATCHED) })
50+
return false
4451
}
4552

4653
inline fun runEventLoop(eventLoop: EventLoop, block: () -> Unit) {
@@ -227,6 +234,12 @@ internal interface DispatchedTask<in T> : Runnable {
227234
}
228235
}
229236

237+
internal fun DispatchedContinuation<Unit>.yield(): Boolean {
238+
return UndispatchedEventLoop.execute(this, Unit, MODE_CANCELLABLE, true) {
239+
run()
240+
}
241+
}
242+
230243
internal fun <T> DispatchedTask<T>.dispatch(mode: Int = MODE_CANCELLABLE) {
231244
val delegate = this.delegate
232245
if (mode.isDispatchedMode && delegate is DispatchedContinuation<*> && mode.isCancellableMode == resumeMode.isCancellableMode) {

common/kotlinx-coroutines-core-common/src/Yield.kt

+3-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ public suspend fun yield(): Unit = suspendCoroutineUninterceptedOrReturn sc@ { u
1919
val context = uCont.context
2020
context.checkCompletion()
2121
val cont = uCont.intercepted() as? DispatchedContinuation<Unit> ?: return@sc Unit
22-
if (!cont.dispatcher.isDispatchNeeded(context)) return@sc Unit
22+
if (!cont.dispatcher.isDispatchNeeded(context)) {
23+
return@sc if (cont.yield()) COROUTINE_SUSPENDED else Unit
24+
}
2325
cont.dispatchYield(Unit)
2426
COROUTINE_SUSPENDED
2527
}

common/kotlinx-coroutines-core-common/src/internal/ArrayQueue.kt

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ internal class ArrayQueue<T : Any> {
88
private var elements = arrayOfNulls<Any>(16)
99
private var head = 0
1010
private var tail = 0
11+
val isEmpty: Boolean get() = head == tail
1112

1213
public fun addLast(element: T) {
1314
elements[tail] = element

common/kotlinx-coroutines-core-common/test/UnconfinedTest.kt

+42-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ class UnconfinedTest : TestBase() {
5454
}
5555

5656
@Test
57-
fun enterMultipleTimes() = runTest {
57+
fun testEnterMultipleTimes() = runTest {
5858
launch(Unconfined) {
5959
expect(1)
6060
}
@@ -70,5 +70,46 @@ class UnconfinedTest : TestBase() {
7070
finish(4)
7171
}
7272

73+
@Test
74+
fun testYield() = runTest {
75+
expect(1)
76+
launch(Dispatchers.Unconfined) {
77+
expect(2)
78+
yield()
79+
launch {
80+
expect(4)
81+
}
82+
expect(3)
83+
yield()
84+
expect(5)
85+
}.join()
86+
87+
finish(6)
88+
}
89+
90+
@Test
91+
fun testCancellationWihYields() = runTest {
92+
expect(1)
93+
GlobalScope.launch(Dispatchers.Unconfined) {
94+
val job = coroutineContext[Job]!!
95+
expect(2)
96+
yield()
97+
GlobalScope.launch(Dispatchers.Unconfined) {
98+
expect(4)
99+
job.cancel()
100+
expect(5)
101+
}
102+
expect(3)
103+
104+
try {
105+
yield()
106+
} finally {
107+
expect(6)
108+
}
109+
}
110+
111+
finish(7)
112+
}
113+
73114
class TestException : Throwable()
74115
}

0 commit comments

Comments
 (0)