This code ends with exception and never prints "Do...
# coroutines
m
This code ends with exception and never prints "Done":
Copy code
fun 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-runTest
d
The proper place to report this is https://github.com/Kotlin/kotlinx.coroutines/issues/, YouTrack is for the standard library. That said, I don't understand why you consider this a problem. If
CoroutineExceptionHandler
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.
y
The real question is why run code after "runTest"?
m
That is to show that my test would fail, thr problem is that runTest throws an exception.
@Dmitry Khalanskiy [JB] Why do you think it should crash my application?
supervisorScope
is there to ignore exceptions from
launch
.
Copy code
fun main() {
    runBlocking {
        supervisorScope {
            launch { throw Exception("Error in coroutine") }
        }
//        println("Done")
    }
    println("Done")
}
// (exception)
// Done
d
When running on non-Android JVM, it indeed won't crash the application.
m
That is an expected use-case:
Copy code
// 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 sent
That should be the same if I start it on a custom scope on Android, I believe it should be the same on Native.
Why do you think it is silently hiding exceptions? I assume
supervisorScope
is particularly used to silence exceptions, or am I missing something?
d
You seemed so very sure that this was a safe pattern that I started doubting myself, but running exactly the code you've provided, I get a crash both in Kotlin/Native and on Android, so
runTest
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/
m
Ok, this behavior might make sense on Android and iOS, but that is not a general behavior of Kotlin Coroutines, just platform-specific behavior.
r
@Dmitry Khalanskiy [JB] am I correct in understanding that a
supervisorScope
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)?
m
supervisorScope
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.
😱 1
p
Seems like an issue.
d
but that is not a general behavior of Kotlin Coroutines
The 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.
supervisorScope
should not cancel other children and should not throw it
Yes. Instead, it becomes the coroutine's own responsibility to either decide what to do with exceptions (and install a
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:
Copy code
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.