marcinmoskala
04/17/2025, 9:49 AMfun main() {
runTest {
supervisorScope {
launch { throw Exception("Error in coroutine") }
}
}
println("Done")
}
That is a result of runTest setting CoroutineExceptionHandler. I do not understand why it is doing that, I only had problems with this behavior. I made an issue:
https://youtrack.jetbrains.com/issue/KT-76854/supervisorScope-misbehaves-in-runTestDmitry Khalanskiy [JB]
04/17/2025, 9:52 AMCoroutineExceptionHandler
was not set, the test would still crash on Native, and on the JVM, it would silently pollute logs, hiding a real issue. On Android, the equivalent code would crash the application. It's entirely reasonable for runTest
to detect such problems and notify you about them.Youssef Shoaib [MOD]
04/17/2025, 9:53 AMmarcinmoskala
04/17/2025, 9:57 AMmarcinmoskala
04/17/2025, 10:00 AMsupervisorScope
is there to ignore exceptions from launch
.marcinmoskala
04/17/2025, 10:00 AMfun main() {
runBlocking {
supervisorScope {
launch { throw Exception("Error in coroutine") }
}
// println("Done")
}
println("Done")
}
// (exception)
// Done
Dmitry Khalanskiy [JB]
04/17/2025, 10:04 AMmarcinmoskala
04/17/2025, 10:05 AM// Started by some request or job
suspend fun sendEmails(emails: List<String>) = supervisorScope {
for (email in emails) {
launch {
sendEmail(email)
}
}
}
suspend fun sendEmail(email: String) {
delay(1000)
if (email == "incorrect") throw Exception("Invalid email")
println("Sent email to $email")
}
// Let's simulate a process of sending emails
fun main() {
runBlocking {
sendEmails(listOf("correct1", "correct2", "incorrect", "correct3", "correct4"))
}
println("All emails sent")
}
Result:
Sent email to correct1
Sent email to correct2
(Exception in logs)
Sent email to correct3
Sent email to correct4
All emails sentmarcinmoskala
04/17/2025, 10:06 AMmarcinmoskala
04/17/2025, 10:09 AMsupervisorScope
is particularly used to silence exceptions, or am I missing something?Dmitry Khalanskiy [JB]
04/17/2025, 12:06 PMrunTest
does correctly find a problem in your code.
From the point of view of coroutines, the behavior is explained here: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-exception-handler/marcinmoskala
04/17/2025, 1:26 PMrkechols
04/17/2025, 10:57 PMsupervisorScope
only prevents one failed child coroutine from canceling other child coroutines if you have set a CoroutineExceptionHandler
? And then a regular coroutineScope
does not let you set a CoroutineExceptionHandler
(or maybe just that setting one does nothing to stop all child coroutines from being canceled when one fails)?marcinmoskala
04/18/2025, 7:53 AMsupervisorScope
is like coroutineScope
that uses SupervisorJob
, so it ignores children exceptions. When one children have an exception in coroutineScope
, all other children get cancelled and coroutineScope
throws this exception. supervisorScope
should not cancel other children and should not throw it. That is unless there is a CoroutineExceptionHandler set that breaks application, like in Android or Native. launch
in supervisorScope
calls CoroutineExceptionHandler
and poof, makes supervisorScope
pointless.PHondogo
04/18/2025, 7:55 AMDmitry Khalanskiy [JB]
04/21/2025, 8:31 PMbut that is not a general behavior of Kotlin CoroutinesThe general behavior of
kotlinx.coroutines
is to propagate uncaught exceptions in a plattform-specific manner. The exceptions are always propagated. They are not ignored. A general approach in the library is that it is incorrect not to process exceptions.
Yes. Instead, it becomes the coroutine's own responsibility to either decide what to do with exceptions (and install ashould not cancel other children and should not throw itsupervisorScope
CoroutineExceptionHandler
that does what it needs to) or ensure they do not happen.
If you are certain you want to silently drop exceptions, you can must explicitly opt into that by specifying the exception handler:
withContext(CoroutineExceptionHandler { _, _ -> }) {
supervisorScope {
}
}
If the requirement to ignore all failures is something you encounter often, you can introduce this as a function in your codebase.