Skip to content

Commit 119e443

Browse files
authored
Fail eagerly during exceptions in isDispatchNeeded (#2733)
That helps to pro-actively catch cases like #2717 and to report such exception in even more robust manner
1 parent 937180f commit 119e443

File tree

3 files changed

+35
-6
lines changed

3 files changed

+35
-6
lines changed

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,19 @@ private inline fun runSafely(completion: Continuation<*>, block: () -> Unit) {
4949
try {
5050
block()
5151
} catch (e: Throwable) {
52-
completion.resumeWith(Result.failure(e))
52+
dispatcherFailure(completion, e)
5353
}
5454
}
55+
56+
private fun dispatcherFailure(completion: Continuation<*>, e: Throwable) {
57+
/*
58+
* This method is invoked when we failed to start a coroutine due to the throwing
59+
* dispatcher implementation or missing Dispatchers.Main.
60+
* This situation is not recoverable, so we are trying to deliver the exception by all means:
61+
* 1) Resume the coroutine with an exception, so it won't prevent its parent from completion
62+
* 2) Rethrow the exception immediately, so it will crash the caller (e.g. when the coroutine had
63+
* no parent or it was async/produce over MainScope).
64+
*/
65+
completion.resumeWith(Result.failure(e))
66+
throw e
67+
}

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,18 @@ class FailFastOnStartTest : TestBase() {
7070
val actor = actor<Int>(Dispatchers.Main, start = CoroutineStart.LAZY) { fail() }
7171
actor.send(1)
7272
}
73-
73+
7474
private fun mainException(e: Throwable): Boolean {
7575
return e is IllegalStateException && e.message?.contains("Module with the Main dispatcher is missing") ?: false
7676
}
77+
78+
@Test
79+
fun testProduceNonChild() = runTest(expected = ::mainException) {
80+
produce<Int>(Job() + Dispatchers.Main) { fail() }
81+
}
82+
83+
@Test
84+
fun testAsyncNonChild() = runTest(expected = ::mainException) {
85+
async<Int>(Job() + Dispatchers.Main) { fail() }
86+
}
7787
}

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class FailingCoroutinesMachineryTest(
3333

3434
private var caught: Throwable? = null
3535
private val latch = CountDownLatch(1)
36-
private var exceptionHandler = CoroutineExceptionHandler { _, t -> caught = t;latch.countDown() }
36+
private var exceptionHandler = CoroutineExceptionHandler { _, t -> caught = t; latch.countDown() }
3737
private val lazyOuterDispatcher = lazy { newFixedThreadPoolContext(1, "") }
3838

3939
private object FailingUpdate : ThreadContextElement<Unit> {
@@ -115,14 +115,20 @@ class FailingCoroutinesMachineryTest(
115115

116116
@Test
117117
fun testElement() = runTest {
118-
launch(NonCancellable + dispatcher.value + exceptionHandler + element) {}
118+
// Top-level throwing dispatcher may rethrow an exception right here
119+
runCatching {
120+
launch(NonCancellable + dispatcher.value + exceptionHandler + element) {}
121+
}
119122
checkException()
120123
}
121124

122125
@Test
123126
fun testNestedElement() = runTest {
124-
launch(NonCancellable + dispatcher.value + exceptionHandler) {
125-
launch(element) { }
127+
// Top-level throwing dispatcher may rethrow an exception right here
128+
runCatching {
129+
launch(NonCancellable + dispatcher.value + exceptionHandler) {
130+
launch(element) { }
131+
}
126132
}
127133
checkException()
128134
}

0 commit comments

Comments
 (0)