Skip to content

Commit 7bdc901

Browse files
authored
Mark the coroutine started with UNDISPATCHED as running (#4077)
Fixes #4058 Additional small fix: the coroutine context injected by `probeCoroutineCreated` is used for `CoroutineStart.UNDISPATCHED` even before the first suspension.
1 parent 617f56b commit 7bdc901

File tree

7 files changed

+63
-28
lines changed

7 files changed

+63
-28
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ package kotlinx.coroutines.internal
33
import kotlin.coroutines.*
44

55
internal expect inline fun <T> probeCoroutineCreated(completion: Continuation<T>): Continuation<T>
6+
7+
internal expect inline fun <T> probeCoroutineResumed(completion: Continuation<T>): Unit

kotlinx-coroutines-core/common/src/intrinsics/Undispatched.kt

Lines changed: 6 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,39 +5,20 @@ import kotlinx.coroutines.internal.*
55
import kotlin.coroutines.*
66
import kotlin.coroutines.intrinsics.*
77

8-
/**
9-
* Use this function to restart a coroutine directly from inside of [suspendCoroutine],
10-
* when the code is already in the context of this coroutine.
11-
* It does not use [ContinuationInterceptor] and does not update the context of the current thread.
12-
*/
13-
internal fun <T> (suspend () -> T).startCoroutineUnintercepted(completion: Continuation<T>) {
14-
startDirect(completion) { actualCompletion ->
15-
startCoroutineUninterceptedOrReturn(actualCompletion)
16-
}
17-
}
18-
198
/**
209
* Use this function to start a new coroutine in [CoroutineStart.UNDISPATCHED] mode &mdash;
2110
* immediately execute the coroutine in the current thread until the next suspension.
2211
* It does not use [ContinuationInterceptor], but updates the context of the current thread for the new coroutine.
2312
*/
2413
internal fun <R, T> (suspend (R) -> T).startCoroutineUndispatched(receiver: R, completion: Continuation<T>) {
25-
startDirect(completion) { actualCompletion ->
26-
withCoroutineContext(completion.context, null) {
27-
startCoroutineUninterceptedOrReturn(receiver, actualCompletion)
28-
}
29-
}
30-
}
31-
32-
/**
33-
* Starts the given [block] immediately in the current stack-frame until the first suspension point.
34-
* This method supports debug probes and thus can intercept completion, thus completion is provided
35-
* as the parameter of [block].
36-
*/
37-
private inline fun <T> startDirect(completion: Continuation<T>, block: (Continuation<T>) -> Any?) {
3814
val actualCompletion = probeCoroutineCreated(completion)
3915
val value = try {
40-
block(actualCompletion)
16+
/* The code below is started immediately in the current stack-frame
17+
* and runs until the first suspension point. */
18+
withCoroutineContext(actualCompletion.context, null) {
19+
probeCoroutineResumed(actualCompletion)
20+
startCoroutineUninterceptedOrReturn(receiver, actualCompletion)
21+
}
4122
} catch (e: Throwable) {
4223
actualCompletion.resumeWithException(e)
4324
return
Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,30 @@
11
package kotlinx.coroutines
22

3-
import kotlinx.coroutines.intrinsics.*
3+
import kotlinx.coroutines.internal.probeCoroutineCreated
4+
import kotlinx.coroutines.internal.probeCoroutineResumed
45
import kotlin.coroutines.*
6+
import kotlin.coroutines.intrinsics.*
57

68
suspend fun <T> withEmptyContext(block: suspend () -> T): T = suspendCoroutine { cont ->
79
block.startCoroutineUnintercepted(Continuation(EmptyCoroutineContext) { cont.resumeWith(it) })
810
}
11+
12+
/**
13+
* Use this function to restart a coroutine directly from inside of [suspendCoroutine],
14+
* when the code is already in the context of this coroutine.
15+
* It does not use [ContinuationInterceptor] and does not update the context of the current thread.
16+
*/
17+
fun <T> (suspend () -> T).startCoroutineUnintercepted(completion: Continuation<T>) {
18+
val actualCompletion = probeCoroutineCreated(completion)
19+
val value = try {
20+
probeCoroutineResumed(actualCompletion)
21+
startCoroutineUninterceptedOrReturn(actualCompletion)
22+
} catch (e: Throwable) {
23+
actualCompletion.resumeWithException(e)
24+
return
25+
}
26+
if (value !== COROUTINE_SUSPENDED) {
27+
@Suppress("UNCHECKED_CAST")
28+
actualCompletion.resume(value as T)
29+
}
30+
}

kotlinx-coroutines-core/jsAndWasmShared/src/internal/ProbesSupport.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@ import kotlin.coroutines.*
44

55
@Suppress("NOTHING_TO_INLINE")
66
internal actual inline fun <T> probeCoroutineCreated(completion: Continuation<T>): Continuation<T> = completion
7+
8+
@Suppress("NOTHING_TO_INLINE")
9+
internal actual inline fun <T> probeCoroutineResumed(completion: Continuation<T>) { }

kotlinx-coroutines-core/jvm/src/internal/ProbesSupport.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
package kotlinx.coroutines.internal
44

55
import kotlin.coroutines.*
6-
import kotlin.coroutines.jvm.internal.probeCoroutineCreated as probe
76

8-
internal actual inline fun <T> probeCoroutineCreated(completion: Continuation<T>): Continuation<T> = probe(completion)
7+
internal actual inline fun <T> probeCoroutineCreated(completion: Continuation<T>): Continuation<T> =
8+
kotlin.coroutines.jvm.internal.probeCoroutineCreated(completion)
9+
10+
internal actual inline fun <T> probeCoroutineResumed(completion: Continuation<T>) {
11+
kotlinx.coroutines.debug.internal.probeCoroutineResumed(completion)
12+
}

kotlinx-coroutines-core/native/src/internal/ProbesSupport.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@ import kotlin.coroutines.*
44

55
@Suppress("NOTHING_TO_INLINE")
66
internal actual inline fun <T> probeCoroutineCreated(completion: Continuation<T>): Continuation<T> = completion
7+
8+
@Suppress("NOTHING_TO_INLINE")
9+
internal actual inline fun <T> probeCoroutineResumed(completion: Continuation<T>) { }

kotlinx-coroutines-debug/test/CoroutinesDumpTest.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,26 @@ class CoroutinesDumpTest : DebugTestBase() {
106106
}
107107
}
108108

109+
/**
110+
* Tests that a coroutine started with [CoroutineStart.UNDISPATCHED] is considered running.
111+
*/
112+
@Test
113+
fun testUndispatchedCoroutineIsRunning() = runBlocking {
114+
val job = launch(Dispatchers.IO, start = CoroutineStart.UNDISPATCHED) { // or launch(Dispatchers.Unconfined)
115+
verifyDump(
116+
"Coroutine \"coroutine#1\":StandaloneCoroutine{Active}@1e4a7dd4, state: RUNNING\n",
117+
ignoredCoroutine = "BlockingCoroutine"
118+
)
119+
delay(Long.MAX_VALUE)
120+
}
121+
verifyDump(
122+
"Coroutine \"coroutine#1\":StandaloneCoroutine{Active}@1e4a7dd4, state: SUSPENDED\n",
123+
ignoredCoroutine = "BlockingCoroutine"
124+
) {
125+
job.cancel()
126+
}
127+
}
128+
109129
@Test
110130
fun testCreationStackTrace() = runBlocking {
111131
val deferred = async(Dispatchers.IO) {

0 commit comments

Comments
 (0)