Skip to content

Commit 2314ea0

Browse files
committed
Fix withTimeout throwing InvalidMutabilityException.
1 parent 28fb12c commit 2314ea0

File tree

10 files changed

+40
-12
lines changed

10 files changed

+40
-12
lines changed

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ public suspend fun <T> withContext(
153153
newContext.ensureActive()
154154
// FAST PATH #1 -- new context is the same as the old one
155155
if (newContext === oldContext) {
156-
val coroutine = ScopeCoroutine(newContext, uCont)
156+
val coroutine = ScopeCoroutine(newContext, uCont, true)
157157
return@sc coroutine.startUndispatchedOrReturn(coroutine, block)
158158
}
159159
// FAST PATH #2 -- the new dispatcher is the same as the old one (something else changed)
@@ -267,7 +267,7 @@ private const val RESUMED = 2
267267
internal class DispatchedCoroutine<in T>(
268268
context: CoroutineContext,
269269
uCont: Continuation<T>
270-
) : ScopeCoroutine<T>(context, uCont) {
270+
) : ScopeCoroutine<T>(context, uCont, true) {
271271
// this is copy-and-paste of a decision state machine inside AbstractionContinuation
272272
// todo: we may some-how abstract it via inline class
273273
private val _decision = atomic(UNDECIDED)

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ public suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R
260260
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
261261
}
262262
return suspendCoroutineUninterceptedOrReturn { uCont ->
263-
val coroutine = ScopeCoroutine(uCont.context, uCont)
263+
val coroutine = ScopeCoroutine(uCont.context, uCont, true)
264264
coroutine.startUndispatchedOrReturn(coroutine, block)
265265
}
266266
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,6 @@ private class SupervisorJobImpl(parent: Job?) : JobImpl(parent) {
6565
private class SupervisorCoroutine<in T>(
6666
context: CoroutineContext,
6767
uCont: Continuation<T>
68-
) : ScopeCoroutine<T>(context, uCont) {
68+
) : ScopeCoroutine<T>(context, uCont, true) {
6969
override fun childCancelled(cause: Throwable): Boolean = false
7070
}

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,12 @@ private fun <U, T: U> setupTimeout(
151151
private class TimeoutCoroutine<U, in T: U>(
152152
@JvmField val time: Long,
153153
uCont: Continuation<U> // unintercepted continuation
154-
) : ScopeCoroutine<T>(uCont.context, uCont), Runnable {
154+
) : ScopeCoroutine<T>(uCont.context, uCont, false), Runnable {
155+
init {
156+
// Kludge for native
157+
if (!isReuseSupportedInPlatform()) initParentForNativeUndispatchedCoroutine()
158+
}
159+
155160
override fun run() {
156161
cancelCoroutine(TimeoutCancellationException(time, this))
157162
}

kotlinx-coroutines-core/common/src/flow/internal/FlowCoroutine.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ internal fun <T> CoroutineScope.flowProduce(
6666
private class FlowCoroutine<T>(
6767
context: CoroutineContext,
6868
uCont: Continuation<T>
69-
) : ScopeCoroutine<T>(context, uCont) {
69+
) : ScopeCoroutine<T>(context, uCont, true) {
7070
public override fun childCancelled(cause: Throwable): Boolean {
7171
if (cause is ChildCancelledException) return true
7272
return cancelImpl(cause)

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

+4-3
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ import kotlin.jvm.*
1313
*/
1414
internal open class ScopeCoroutine<in T>(
1515
context: CoroutineContext,
16-
uCont: Continuation<T>
17-
) : AbstractCoroutine<T>(context, true, true), CoroutineStackFrame {
16+
uCont: Continuation<T>,
17+
initParentJob: Boolean
18+
) : AbstractCoroutine<T>(context, initParentJob, true), CoroutineStackFrame {
1819
@JvmField
1920
val uCont: Continuation<T> = uCont.asShareable() // unintercepted continuation, shareable
2021
final override val callerFrame: CoroutineStackFrame? get() = uCont.asLocal() as? CoroutineStackFrame
@@ -25,7 +26,7 @@ internal open class ScopeCoroutine<in T>(
2526

2627
init {
2728
// Kludge for native
28-
if (!isReuseSupportedInPlatform()) initParentForNativeUndispatchedCoroutine()
29+
if (initParentJob && !isReuseSupportedInPlatform()) initParentForNativeUndispatchedCoroutine()
2930
}
3031

3132
protected open fun initParentForNativeUndispatchedCoroutine() {

kotlinx-coroutines-core/js/src/CoroutineContext.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,6 @@ internal actual val CoroutineContext.coroutineName: String? get() = null // not
5757
internal actual class UndispatchedCoroutine<in T> actual constructor(
5858
context: CoroutineContext,
5959
uCont: Continuation<T>
60-
) : ScopeCoroutine<T>(context, uCont) {
60+
) : ScopeCoroutine<T>(context, uCont, tr ue) {
6161
override fun afterResume(state: Any?) = uCont.resumeWith(recoverResult(state, uCont))
6262
}

kotlinx-coroutines-core/jvm/src/CoroutineContext.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ private object UndispatchedMarker: CoroutineContext.Element, CoroutineContext.Ke
113113
internal actual class UndispatchedCoroutine<in T>actual constructor (
114114
context: CoroutineContext,
115115
uCont: Continuation<T>
116-
) : ScopeCoroutine<T>(if (context[UndispatchedMarker] == null) context + UndispatchedMarker else context, uCont) {
116+
) : ScopeCoroutine<T>(if (context[UndispatchedMarker] == null) context + UndispatchedMarker else context, uCont, true) {
117117

118118
private var savedContext: CoroutineContext? = null
119119
private var savedOldValue: Any? = null

kotlinx-coroutines-core/native/src/CoroutineContext.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,6 @@ internal actual val CoroutineContext.coroutineName: String? get() = null // not
4343
internal actual class UndispatchedCoroutine<in T> actual constructor(
4444
context: CoroutineContext,
4545
uCont: Continuation<T>
46-
) : ScopeCoroutine<T>(context, uCont) {
46+
) : ScopeCoroutine<T>(context, uCont, true) {
4747
override fun afterResume(state: Any?) = uCont.resumeWith(recoverResult(state, uCont))
4848
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright 2016-2021 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.test.*
8+
import kotlin.time.*
9+
import kotlin.native.concurrent.freeze
10+
11+
class WithTimeoutNativeTest {
12+
@OptIn(ExperimentalTime::class)
13+
@Test
14+
fun `withTimeout should not raise an exception when parent job is frozen`() = runBlocking {
15+
this.coroutineContext[Job.Key]!!.freeze()
16+
try {
17+
withTimeout(Duration.seconds(Int.MAX_VALUE)) {}
18+
} catch (error: Throwable) {
19+
fail("withTimeout has raised an exception.", error)
20+
}
21+
}
22+
}

0 commit comments

Comments
 (0)