Circular exception with async/await in coroutineScope
See original GitHub issueA circular JobCancellationException
from coroutines keeps causing a StackOverflowException
in logback which I use for logging.
IMO the recursion should be broken up at some point.
This is quite similar to #305.
Output:
java.lang.RuntimeException: 1
suppressed: java.lang.RuntimeException: 2
cause: kotlinx.coroutines.JobCancellationException: Parent job is Cancelling; job=ScopeCoroutine{Cancelled}@257ed010
cause: java.lang.RuntimeException: 1 // RECURSION!
Repro:
suspend fun main() {
try {
coroutineScope {
val def1: Deferred<Unit> = async {
delay(100)
throw RuntimeException("1")
}
val def2: Deferred<Unit> = async {
try {
while (true)
delay(10)
}
catch (e: Exception) {
throw RuntimeException("2", e)
}
}
def1.await()
def2.await()
}
}
catch (throwable: Throwable) {
debug(throwable)
}
}
fun debug(throwable: Throwable, prefix: String = "", processed: MutableSet<Throwable> = hashSetOf()) {
print(throwable)
if (processed.contains(throwable)) {
println(" // RECURSION!")
return
}
println()
processed += throwable
throwable.cause?.let {
print("$prefix cause: ")
debug(it, prefix = "$prefix ", processed = processed)
}
throwable.suppressed?.firstOrNull()?.let {
print("$prefix suppressed: ")
debug(it, prefix = "$prefix ", processed = processed)
}
}
Workaround is to catch CancellationException
and rethrow it without wrapping it into another exception.
Issue Analytics
- State:
- Created 4 years ago
- Reactions:8
- Comments:12 (5 by maintainers)
Top Results From Across the Web
Kotlin: How to wait for a coroutine from non-suspend without ...
So to call a suspend function from a non-suspend function, without using runBlocking, you have to create a coroutine scope.
Read more >Exceptions in coroutines. Cancellation and Exceptions in…
Imagine a UI-related CoroutineScope that processes user interactions. If a child coroutine throws an exception, the UI scope will be cancelled and the...
Read more >Async/Await Error Handling - Beginner JavaScript - Wes Bos
We will talk about error handling strategies for async await in this lesson. Because there is no .then() that we are chaining on...
Read more >Async Operations with Kotlin Coroutines — Part 1
Suspending functions; Coroutine context and Coroutine Scope ... coroutine with a return value or with an exception if an error had occurred ...
Read more >Python behind the scenes #12: how async/await works in Python
Python's implementation of async / await adds even more concepts to this ... If the generator returns something, the exception holds the ...
Read more >
Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
I understand your pain with circular exceptions and I would like to fix this problem as well.
It is indeed okay to override
getCause
, but making it non-pure is not: it will confuse both developers and some frameworks (e.g. I am not sure that all logging frameworks handle exceptions with non-puregetCause
without bugs).It is another layer of complications because coroutine may not have a context (e.g. just a
Job()
created without a coroutine). Additional complexity comes from the fact that some coroutine handle their own exceptions, while others don’t etc.What I think we can do is to use
CE
without a cause when coroutine has a parent, though I am not sure this is a valid change. I will investigate it furtherThank you for the detailed explanation. I understand the cause and the reasoning. Yet it ends up in a cycle and cycles don’t make sense for exceptions.
Unfortunately that puts our case in kind of a deadlock between this issue with Kotlin coroutines and logback which doesn’t support circular exceptions although the issue is open for 5 years now - resulting in a
StackOverflowError
. Bottom line is that we lose some stability in the back-end, which is not good. These issues are difficult to foresee while programming because CEs can potentially be thrown all over the place, so there would be a need for a lot of checking for CEs and a lack of context if we cannot wrap it.What about changing the CE’s cause dynamically depending on the coroutine context?
cause
returns the cause (RE(1)
) because otherwise that information would be inaccessible.cause
then returnsnull
. In the parent the causeRE(1)
is still accessible because it’s the root exception and we merely have broken the cycle.According to Throwable.getCause() documentation it is fine to override the method.