Skip to content

Commit 8389768

Browse files
committed
Rethrow dispatcher exceptions during the undispatched coroutines start immediately
Otherwise, in the face of malfunctioning dispatcher, there might be no trace of any error, making it extremely hard to debug. The root cause should be addressed by #4209 Fixes #4142
1 parent b96c9a2 commit 8389768

File tree

2 files changed

+37
-0
lines changed

2 files changed

+37
-0
lines changed

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

+9
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ private fun <T, R> ScopeCoroutine<T>.startUndspatched(
6464
): Any? {
6565
val result = try {
6666
block.startCoroutineUninterceptedOrReturn(receiver, this)
67+
} catch (e: DispatchException) {
68+
// Special codepath for failing CoroutineDispatcher: rethrow an exception
69+
// immediately without waiting for children to indicate something is wrong
70+
dispatchException(e)
6771
} catch (e: Throwable) {
6872
CompletedExceptionally(e)
6973
}
@@ -93,3 +97,8 @@ private fun <T, R> ScopeCoroutine<T>.startUndspatched(
9397
private fun ScopeCoroutine<*>.notOwnTimeout(cause: Throwable): Boolean {
9498
return cause !is TimeoutCancellationException || cause.coroutine !== this
9599
}
100+
101+
private fun ScopeCoroutine<*>.dispatchException(e: DispatchException) {
102+
makeCompleting(CompletedExceptionally(e.cause))
103+
throw throw recoverStackTrace(e.cause, uCont)
104+
}

kotlinx-coroutines-core/jvm/test/FailFastOnStartTest.kt

+28
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,17 @@ package kotlinx.coroutines
44

55
import kotlinx.coroutines.testing.*
66
import kotlinx.coroutines.channels.*
7+
import kotlinx.coroutines.flow.emptyFlow
8+
import kotlinx.coroutines.flow.flow
9+
import kotlinx.coroutines.flow.flowOn
710
import org.junit.*
811
import org.junit.Test
912
import org.junit.rules.*
13+
import org.junit.runners.model.TestTimedOutException
14+
import java.util.concurrent.TimeoutException
15+
import kotlin.coroutines.Continuation
16+
import kotlin.coroutines.EmptyCoroutineContext
17+
import kotlin.coroutines.startCoroutine
1018
import kotlin.test.*
1119

1220
class FailFastOnStartTest : TestBase() {
@@ -81,4 +89,24 @@ class FailFastOnStartTest : TestBase() {
8189
fun testAsyncNonChild() = runTest(expected = ::mainException) {
8290
async<Int>(Job() + Dispatchers.Main) { fail() }
8391
}
92+
93+
@Test
94+
fun testFlowOn() {
95+
// See #4142, this test ensures that `coroutineScope { produce(failingDispatcher, ATOMIC) }`
96+
// rethrows an exception. It does not help with the completion of such a coroutine though.
97+
// Hack to avoid waiting for test completion
98+
expect(1)
99+
val caller = suspend {
100+
try {
101+
emptyFlow<Int>().flowOn(Dispatchers.Main).collect { fail() }
102+
} catch (e: Throwable) {
103+
assertTrue(mainException(e))
104+
expect(2)
105+
}
106+
}
107+
108+
caller.startCoroutine(Continuation(EmptyCoroutineContext) {
109+
finish(3)
110+
})
111+
}
84112
}

0 commit comments

Comments
 (0)