Skip to content

Commit ab79214

Browse files
committed
Support yield in immediate dispatchers
As special internal fun CoroutineDispatcher.isDispatchSupported is introduced to special-case Unconfined dispatcher that cannot be dispatched to. All other dispatcher return `true`. This gives a little more freedom in distinguishing dispatchers | isDispNeeded | isDispSupported Regular | true | true Immediate | depends | true Unconfined | false | false So yield now checks for "isDispatchSupported" and works properly for immediate dispatchers. Fixes #1474
1 parent 1da7311 commit ab79214

File tree

4 files changed

+48
-1
lines changed

4 files changed

+48
-1
lines changed

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

+9
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,15 @@ public abstract class CoroutineDispatcher :
7979
*/
8080
public abstract fun dispatch(context: CoroutineContext, block: Runnable)
8181

82+
/**
83+
* Reserved for [Dispatchers.Unconfined] that returns `false` here because its
84+
* [dispatch] method throws [UnsupportedOperationException]. It is used in [yield] implementation.
85+
*
86+
* @suppress **This an internal API and should not be used from general code.**
87+
*/
88+
@InternalCoroutinesApi
89+
public open fun isDispatchSupported(context: CoroutineContext): Boolean = true
90+
8291
/**
8392
* Dispatches execution of a runnable `block` onto another thread in the given `context`
8493
* with a hint for the dispatcher that the current dispatch is triggered by a [yield] call, so that the execution of this

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

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import kotlin.coroutines.*
1111
*/
1212
internal object Unconfined : CoroutineDispatcher() {
1313
override fun isDispatchNeeded(context: CoroutineContext): Boolean = false
14+
override fun isDispatchSupported(context: CoroutineContext): Boolean = false
1415
override fun dispatch(context: CoroutineContext, block: Runnable) { throw UnsupportedOperationException() }
1516
override fun toString(): String = "Unconfined"
1617
}

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)) {
22+
// Special case for Unconfined dispatcher that does not support the concept of "dispatch" and where
23+
// "dispatchYield" function cannot be used.
24+
if (!cont.dispatcher.isDispatchSupported(context)) {
2325
return@sc if (cont.yieldUndispatched()) COROUTINE_SUSPENDED else Unit
2426
}
2527
cont.dispatchYield(Unit)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2016-2019 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 kotlin.coroutines.*
8+
import kotlin.test.*
9+
10+
class ImmediateYieldTest : TestBase() {
11+
12+
// See https://github.com/Kotlin/kotlinx.coroutines/issues/1474
13+
@Test
14+
fun testImmediateYield() = runTest {
15+
expect(1)
16+
launch(ImmediateDispatcher(coroutineContext[ContinuationInterceptor])) {
17+
expect(2)
18+
yield()
19+
expect(4)
20+
}
21+
expect(3) // after yield
22+
yield() // yield back
23+
finish(5)
24+
}
25+
26+
// imitate immediate dispatcher
27+
private class ImmediateDispatcher(job: ContinuationInterceptor?) : CoroutineDispatcher() {
28+
val delegate: CoroutineDispatcher = job as CoroutineDispatcher
29+
30+
override fun isDispatchNeeded(context: CoroutineContext): Boolean = false
31+
32+
override fun dispatch(context: CoroutineContext, block: Runnable) =
33+
delegate.dispatch(context, block)
34+
}
35+
}

0 commit comments

Comments
 (0)