Skip to content

Commit 2808f09

Browse files
committed
Optimize yield via dispatcher.isDispatchNeeded
Also, as it was before, throw UnsupportedOperationException from the Dispatcher.Unconfined.dispatch method in case some code wraps the Unconfined dispatcher but fails to delegate isDispatchNeeded properly.
1 parent 8b2a6a9 commit 2808f09

File tree

4 files changed

+25
-16
lines changed

4 files changed

+25
-16
lines changed

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,9 @@ public abstract class CoroutineDispatcher :
7878
* may leave the coroutines that use this dispatcher in the inconsistent and hard to debug state.
7979
*
8080
* **Note**: This method must not immediately call [block]. Doing so would result in [StackOverflowError]
81-
* when [yield] is repeatedly called from a loop. However, an implementation can delegate this function
82-
* to `dispatch` method of [Dispatchers.Unconfined], which is integrated with [yield] to avoid this problem.
81+
* when [yield] is repeatedly called from a loop. However, an implementation that returns `false` from
82+
* [isDispatchNeeded] can delegate this function to `dispatch` method of [Dispatchers.Unconfined], which is
83+
* integrated with [yield] to avoid this problem.
8384
*/
8485
public abstract fun dispatch(context: CoroutineContext, block: Runnable)
8586

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

+4-3
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,16 @@ internal object Unconfined : CoroutineDispatcher() {
1414
override fun isDispatchNeeded(context: CoroutineContext): Boolean = false
1515

1616
override fun dispatch(context: CoroutineContext, block: Runnable) {
17-
// Just in case somebody wraps Unconfined dispatcher casing the "dispatch" to be called from "yield"
18-
// See also code of "yield" function
17+
// It can only be called by the "yield" function. See also code of "yield" function.
1918
val yieldContext = context[YieldContext]
2019
if (yieldContext != null) {
2120
// report to "yield" that it is an unconfined dispatcher and don't call "block.run()"
2221
yieldContext.dispatcherWasUnconfined = true
2322
return
2423
}
25-
block.run()
24+
throw UnsupportedOperationException("Dispatchers.Unconfined.dispatch function can only be used by the yield function. " +
25+
"If you wrap Unconfined dispatcher in your code, make sure you properly delegate " +
26+
"isDispatchNeeded and dispatch calls.")
2627
}
2728

2829
override fun toString(): String = "Unconfined"

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

+15-9
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,22 @@ public suspend fun yield(): Unit = suspendCoroutineUninterceptedOrReturn sc@ { u
2727
val context = uCont.context
2828
context.checkCompletion()
2929
val cont = uCont.intercepted() as? DispatchedContinuation<Unit> ?: return@sc Unit
30-
// This code detects the Unconfined dispatcher even if it was wrapped into another dispatcher
31-
val yieldContext = YieldContext()
32-
cont.dispatchYield(context + yieldContext, Unit)
33-
// Special case for the unconfined dispatcher that can yield only in existing unconfined loop
34-
if (yieldContext.dispatcherWasUnconfined) {
35-
// Means that the Unconfined dispatcher got the call, but did not do anything.
36-
// See also code of "Unconfined.dispatch" function.
37-
return@sc if (cont.yieldUndispatched()) COROUTINE_SUSPENDED else Unit
30+
if (cont.dispatcher.isDispatchNeeded(context)) {
31+
// this is a regular dispatcher -- do simple dispatchYield
32+
cont.dispatchYield(context, Unit)
33+
} else {
34+
// This is either an "immediate" dispatcher or the Unconfined dispatcher
35+
// This code detects the Unconfined dispatcher even if it was wrapped into another dispatcher
36+
val yieldContext = YieldContext()
37+
cont.dispatchYield(context + yieldContext, Unit)
38+
// Special case for the unconfined dispatcher that can yield only in existing unconfined loop
39+
if (yieldContext.dispatcherWasUnconfined) {
40+
// Means that the Unconfined dispatcher got the call, but did not do anything.
41+
// See also code of "Unconfined.dispatch" function.
42+
return@sc if (cont.yieldUndispatched()) COROUTINE_SUSPENDED else Unit
43+
}
44+
// Otherwise, it was some other dispatcher that successfully dispatched the coroutine
3845
}
39-
// It was some other dispatcher that successfully dispatched the coroutine
4046
COROUTINE_SUSPENDED
4147
}
4248

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,10 @@ public class RecoverableTestCancellationException(message: String? = null) : Can
7171
public fun wrapperDispatcher(context: CoroutineContext): CoroutineContext {
7272
val dispatcher = context[ContinuationInterceptor] as CoroutineDispatcher
7373
return object : CoroutineDispatcher() {
74-
override fun dispatch(context: CoroutineContext, block: Runnable) {
74+
override fun isDispatchNeeded(context: CoroutineContext): Boolean =
75+
dispatcher.isDispatchNeeded(context)
76+
override fun dispatch(context: CoroutineContext, block: Runnable) =
7577
dispatcher.dispatch(context, block)
76-
}
7778
}
7879
}
7980

0 commit comments

Comments
 (0)