Hi all, I’ve found what I consider to be a bug with coroutines and exception handling.
In summary, if you throw an exception inside a suspending function, and the exception either has no cause, or has a cause with a different type to the exception, when the exception is thrown from the coroutine, it’s actually a new exception of the same type as the original exception, which has the original exception as a cause.
You can find my reproducible example here: coroutines-exception-bug/src/main/kotlin/au/com/skater901/Main.kt at main · Skater901/coroutines-exception-bug · GitHub
If you run my code, you’ll find that instead of catching and logging an IllegalArgumentException
with no cause, it actually catches and logs an IllegalArgumentException
which has a cause of IllegalArgumentException
with the same message.
The reason this happens is because of these two functions: causeAndStacktrace()
, and tryCopyException(exception: E)
.
The logic inside causeAndStackTrace()
looks wrong to me; the main if
condition is only triggered if the exception has a cause, and the cause is the same type as the exception. I don’t understand why it’s written this way; I would assume that in the majority of cases, the cause would NOT be the same type as the parent exception.
The other problem is tryCopyException()
; it doesn’t really create a copy of the exception, but rather it replaces the existing exception with a new exception of the same type which has the original exception as the cause of this new exception.
Now it is worth noting that this only happens if coroutine debugging is turned on, but I’m finding with my work projects that coroutine debugging is turned on for automated testing, and I’m not sure why. The system property isn’t set, but the ASSERTIONS_ENABLED
property evaluates to true, and I have no idea why.
That said, I don’t think that turning on debugging for coroutines should fundamentally change how exceptions are handled, resulting in this weird exception duplication.
You can fix this problem in my example by commenting out the coroutine debug line which is pretty obvious - this bug only happens when coroutine debugging is enabled - or by commenting out the withContext. Certain coroutine types don’t follow the code path that leads to those functions.
To close, I don’t know if this is actually an intended design feature of the code, but if it is, I’d really like an explanation on why, and what I can do about it to avoid my exception handling code getting all messed up. Thanks!