File tree 7 files changed +54
-11
lines changed
kotlinx-coroutines-core/common
7 files changed +54
-11
lines changed Original file line number Diff line number Diff line change @@ -133,7 +133,7 @@ public abstract class AbstractCoroutine<in T>(
133
133
*
134
134
* * [DEFAULT] uses [startCoroutineCancellable].
135
135
* * [ATOMIC] uses [startCoroutine].
136
- * * [UNDISPATCHED ] uses [startCoroutineUndispatched].
136
+ * * [UNCONFINED ] uses [startCoroutineUndispatched].
137
137
* * [LAZY] does nothing.
138
138
*/
139
139
public fun start (start : CoroutineStart , block : suspend () -> T ) {
@@ -150,7 +150,7 @@ public abstract class AbstractCoroutine<in T>(
150
150
*
151
151
* * [DEFAULT] uses [startCoroutineCancellable].
152
152
* * [ATOMIC] uses [startCoroutine].
153
- * * [UNDISPATCHED ] uses [startCoroutineUndispatched].
153
+ * * [UNCONFINED ] uses [startCoroutineUndispatched].
154
154
* * [LAZY] does nothing.
155
155
*/
156
156
public fun <R > start (start : CoroutineStart , receiver : R , block : suspend R .() -> T ) {
Original file line number Diff line number Diff line change @@ -76,6 +76,10 @@ public abstract class CoroutineDispatcher :
76
76
*
77
77
* This method should generally be exception-safe. An exception thrown from this method
78
78
* may leave the coroutines that use this dispatcher in the inconsistent and hard to debug state.
79
+ *
80
+ * **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.
79
83
*/
80
84
public abstract fun dispatch (context : CoroutineContext , block : Runnable )
81
85
Original file line number Diff line number Diff line change @@ -27,7 +27,7 @@ public enum class CoroutineStart {
27
27
* invoked the coroutine builder continues execution.
28
28
*
29
29
* Note that [Dispatchers.Unconfined] always returns `false` from its [CoroutineDispatcher.isDispatchNeeded]
30
- * function, so starting a coroutine with [Dispatchers.Unconfined] by [DEFAULT] is the same as using [UNDISPATCHED ].
30
+ * function, so starting a coroutine with [Dispatchers.Unconfined] by [DEFAULT] is the same as using [UNCONFINED ].
31
31
*
32
32
* If coroutine [Job] is cancelled before it even had a chance to start executing, then it will not start its
33
33
* execution at all, but will complete with an exception.
@@ -79,7 +79,7 @@ public enum class CoroutineStart {
79
79
*
80
80
* * [DEFAULT] uses [startCoroutineCancellable].
81
81
* * [ATOMIC] uses [startCoroutine].
82
- * * [UNDISPATCHED ] uses [startCoroutineUndispatched].
82
+ * * [UNCONFINED ] uses [startCoroutineUndispatched].
83
83
* * [LAZY] does nothing.
84
84
*
85
85
* @suppress **This an internal API and should not be used from general code.**
@@ -98,7 +98,7 @@ public enum class CoroutineStart {
98
98
*
99
99
* * [DEFAULT] uses [startCoroutineCancellable].
100
100
* * [ATOMIC] uses [startCoroutine].
101
- * * [UNDISPATCHED ] uses [startCoroutineUndispatched].
101
+ * * [UNCONFINED ] uses [startCoroutineUndispatched].
102
102
* * [LAZY] does nothing.
103
103
*
104
104
* @suppress **This an internal API and should not be used from general code.**
Original file line number Diff line number Diff line change 5
5
package kotlinx.coroutines
6
6
7
7
import kotlin.coroutines.*
8
+ import kotlin.jvm.*
8
9
9
10
/* *
10
11
* A coroutine dispatcher that is not confined to any specific thread.
11
12
*/
12
13
internal object Unconfined : CoroutineDispatcher() {
13
14
override fun isDispatchNeeded (context : CoroutineContext ): Boolean = false
14
- // Just in case somebody wraps Unconfined dispatcher casing the "dispatch" to be called from "yield"
15
- override fun dispatch (context : CoroutineContext , block : Runnable ) = block.run ()
15
+
16
+ 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
19
+ val yieldContext = context[YieldContext ]
20
+ if (yieldContext != null ) {
21
+ // report to "yield" that it is an unconfined dispatcher and don't call "block.run()"
22
+ yieldContext.dispatcherWasUnconfined = true
23
+ return
24
+ }
25
+ block.run ()
26
+ }
27
+
16
28
override fun toString (): String = " Unconfined"
17
29
}
30
+
31
+ /* *
32
+ * Used to detect calls to [Unconfined.dispatch] from [yield] function.
33
+ */
34
+ internal class YieldContext : AbstractCoroutineContextElement (Key ) {
35
+ companion object Key : CoroutineContext.Key<YieldContext>
36
+
37
+ @JvmField
38
+ var dispatcherWasUnconfined = false
39
+ }
Original file line number Diff line number Diff line change @@ -27,9 +27,16 @@ public suspend fun yield(): Unit = suspendCoroutineUninterceptedOrReturn sc@ { u
27
27
val context = uCont.context
28
28
context.checkCompletion()
29
29
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 )
30
33
// Special case for the unconfined dispatcher that can yield only in existing unconfined loop
31
- if (cont.dispatcher == = Unconfined ) return @sc if (cont.yieldUndispatched()) COROUTINE_SUSPENDED else Unit
32
- cont.dispatchYield(Unit )
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
38
+ }
39
+ // It was some other dispatcher that successfully dispatched the coroutine
33
40
COROUTINE_SUSPENDED
34
41
}
35
42
Original file line number Diff line number Diff line change @@ -211,8 +211,7 @@ internal class DispatchedContinuation<in T>(
211
211
}
212
212
213
213
// used by "yield" implementation
214
- internal fun dispatchYield (value : T ) {
215
- val context = continuation.context
214
+ internal fun dispatchYield (context : CoroutineContext , value : T ) {
216
215
_state = value
217
216
resumeMode = MODE_CANCELLABLE
218
217
dispatcher.dispatchYield(context, this )
Original file line number Diff line number Diff line change @@ -43,4 +43,15 @@ class ImmediateYieldTest : TestBase() {
43
43
}
44
44
finish(4 ) // after launch
45
45
}
46
+
47
+ @Test
48
+ fun testWrappedUnconfinedDispatcherYieldStackOverflow () = runTest {
49
+ expect(1 )
50
+ withContext(wrapperDispatcher(Dispatchers .Unconfined )) {
51
+ repeat(100_000 ) {
52
+ yield ()
53
+ }
54
+ }
55
+ finish(2 )
56
+ }
46
57
}
You can’t perform that action at this time.
0 commit comments