https://kotlinlang.org logo
Title
n

Norbi

09/17/2021, 10:05 AM
Do I understand correctly that if I catch an exception in a coroutine I should always check if it is a
CancellationException
? Eg.:
suspend fun etc() {
    try {
        someOtherSuspendFun()
    } catch (e: Exception) {
        if (e is CancellationException) {
            throw e // The operation has also failed but I should not throw WrapperException because it will interfere with the cancellation mechanism
        } else {
            throw WrapperException("Operation failed", e)
        }
    }
}
(The above is pseudo code, I have not tried it.) Thanks.
A related question: why isn't cancellation implemented using an
Error
instead an
Exception
?
s

streetsofboston

09/17/2021, 11:59 AM
Another related question, why does a catch (as part of a try) clause automatically 'eat' and not rethrow the exception? I wish that you'd have cancel/eat the thrown exception to stop it from being rethrown up the stack. This would allow for side-effect handling (eg logging) of exceptions without the risk of forgetting to rethrow them. (I know the answer a bit, it's probably related to Java and its design of exception handling)
But as an answer to your original question: Yes, rethrow CancellationExceptions. If you don't, you break Structured Concurrency.
👍🏻 1
o

Oliver.O

09/17/2021, 1:41 PM
And if it's just about adding context information to any kind of exception, better catch
Throwable
.
n

Norbi

09/17/2021, 2:52 PM
"Yes, rethrow CancellationExceptions" - Am I the only one who thinks this is not well designed? 🤨
"better catch `Throwable`" - I think it is not advised to catch `Error`s in most cases. This is why I thought that Cancellation*Error* would be more appropriate than Cancellation*Exception*.
"it's probably related to Java and its design of exception handling" - The Kotlin compiler would be able to generate an
if (e is CancellationException) ...
condition at the start of the catch block (or event a dedicated
catch (e: CancellationException)
block) if it wanted to. So it must be an intentional design decision but I don't get it because it's too easy to forget to rethrow
CancellationException
.
e

ephemient

09/17/2021, 4:12 PM
Java's InterruptedException is also an exception and not an Error, despite it being necessary to take extra care around catching it
swallowing anything other that specific known throwables (exception or error or not) has always been danger
💯 2
o

Oliver.O

09/17/2021, 4:34 PM
@Norbi Imagine what code would look like if you adhere to these recommendations:
The first and foremost use of exceptions in Kotlin is to handle program logic errors.
...
As a rule of thumb, you should not be catching exceptions in general Kotlin code. That’s a code smell. Exceptions should be handled by some top-level framework code of your application to alert developers of the bugs in the code and to restart your application or its affected operation. That’s the primary purpose of exceptions in Kotlin.
More on the design background is presented here: https://elizarov.medium.com/kotlin-and-exceptions-8062f589d07
n

Norbi

09/17/2021, 5:40 PM
@Oliver.O, thanks for the info. The article is interesting although I don't fully agree. Exceptions are often caught and rethrown wrapped in a new exception with additional contextual information. It would be difficult/painful to implement a similar "recursive" data structure for error results. Besides, to investigate a failure the stack trace is very useful, without them you may have to lookup lots of log entries to extract the same information.
o

Oliver.O

09/17/2021, 6:04 PM
If you feel you frequently need such wrapping, why not just write a small helper function that makes it more convenient, such as:
inline fun <T, R> T.runCatchingCancellable(block: T.() -> R): Result<R> {
    return try {
        Result.success(block())
    } catch (c: CancellationException) {
        throw c
    } catch (e: Throwable) {
        Result.failure(e)
    }
}

suspend fun etc() {
    runCatchingCancellable {
        someOtherSuspendFun()
    }.onFailure {
        throw WrapperException("Operation failed", it)
    }
}
🙌🏻 1
🙌 1
Something like this has been discussed here: https://github.com/Kotlin/kotlinx.coroutines/issues/1814
👀 1
👍🏻 1