Djuro
03/08/2024, 10:16 AMsuspend
functions?
I know that try/catch
and runCatching
swallow the CancellationException
.
Do you recommend wrapping the exception within coroutineScope
which rethrows the exceptions at the call site like this
runCatching {
coroutineScope {/*code*/}
}.OnFailure {
// handle
}
I also use runSuspendCatching
in some projects, what do you think about that approach?Dmitry Khalanskiy [JB]
03/08/2024, 10:24 AMtry/catch
doesn't swallow exceptions if you only catch the exceptions that you need to handle.Joffrey
03/08/2024, 12:59 PMrunCatching
as delicate? More and more people seem to try to use it for railway programming, and don't realize they are catching too much with it. It seems we made it too easy to do the wrong thing with this function.Djuro
03/08/2024, 1:11 PMDmitry Khalanskiy [JB]
03/08/2024, 2:13 PMI should always be aware of which exceptions I am handling?Yes, sure. When you are asking the program to do something, why could you not care about why it failed to do so? There are some rare situations like "I'm asking the program to do something, and it would be nice if it did that, but I don't actually care," but they are rare.
when I just don't care what type of exception it isTough to imagine this situation. Do you really not care if you get an
OutOfMemoryError
? StackOverflowError
? ThreadDeath
? runCatching
will catch all of them, even though the only option when you receive them is usually to crash the program.
What about cases when working with some old api that's not documented wellThis question is too abstract, tough to say anything definite. However, it's tough for me to imagine why you wouldn't care about why some API failed. Let's say that, during testing, you found out that the undocumented function
startServer
can fail with IllegalArgumentException
and IllegalStateException
. You add try
-catch
blocks for these scenarios. Then, in a month, suddenly, this API crashes with UnsupportedOperationException
each time it's called on some device. Do you really not care that this function failed with something you didn't anticipate?Dmitry Khalanskiy [JB]
03/08/2024, 2:15 PMcatch (e: Throwable)
and a dozen calls to runCatching
in kotlinx-coroutines
. There are some cases where this is the best or even the only choice. But using runCatching
should be a deliberate choice, not the default option.Dmitry Khalanskiy [JB]
03/08/2024, 2:40 PMPablichjenkov
03/08/2024, 2:41 PMpublic inline fun <T, R> T.cooperativeCatch(block: T.() -> R): Result<R> {
return try {
Result.success(block())
} catch (e: CancellationException) {
throw e
} catch (e: Throwable) {
Result.failure(e)
}
}
Dmitry Khalanskiy [JB]
03/08/2024, 2:45 PMcooperativeCatch { deferred.await() }
pattern (where deferred
is a Deferred<T>
): https://github.com/Kotlin/kotlinx.coroutines/issues/1814#issuecomment-1943224856Pablichjenkov
03/08/2024, 2:50 PMCLOVIS
03/08/2024, 4:34 PMJoffrey
03/08/2024, 5:27 PMrunCatching
is mostly an anti-pattern in general, and coroutine exceptions are just one obvious example. People start to ask about band-aids for `runCatching`+coroutines but the problem is more general.Djuro
03/08/2024, 6:12 PMDjuro
03/08/2024, 6:16 PMrunCatching
is an anti pattern in general, how isnt try/catch
also an anti pattern?CLOVIS
03/08/2024, 6:17 PMcatch(e: Throwable)
is the exact same anti-pattern as runCatching
. catch(e: TheSpecificExceptionIKnowThisCodeMayThrow)
is fine.
The anti-pattern isn't catch itself, it's “catching absolutely anything that could happen indescriminately”. Much like having variables of type Any
is an anti-pattern but any other type is fine. Even non-concrete types like `List`are fine because they still communicate intent.Djuro
03/08/2024, 6:21 PMjuliocbcotta
03/09/2024, 2:06 AMCLOVIS
03/09/2024, 4:12 PMI may be "downloading a file that is too big to fit in memory", should my app crash or should I just present an error to the user?Catching the
OutOfMemoryException
will not stop the app from crashing. When OOM is thrown, it's very likely that anything you do will still lead to the app crashing, unless you are able to free a lot of memory directly in the catch block (and even then, I'm unsure it's enough for the JVM to continue working). This is what the previous users were saying: you cannot realistically recover from most non-Exception throwables. You may say you want to, but runCatching doesn't let you do that.
runcatching is not an ant pattern, it does what we wantit doesn't.
we now need a specialization of it to handle coroutines better.Use either.catch.
Dmitry Khalanskiy [JB]
03/09/2024, 5:36 PMrunCatching
is okay, or the app crashes. But that's a false dichotomy. The use case you are describing (having global catch (e: Throwable)
around a huge block of logic) is only a tiny portion of error handling, and having a bit more ceremony around these parts (explicitly writing out try/catch blocks, with comments that this is intentional, maybe wrapped in some code to retry the operation a couple of times) shouldn't be a problem. The issue with runCatching
is that it's a shorthand version of something that should not be taken lightly.
There are use cases for wiping out all user data on your PC. There should be a way to do so. It would still be surprising if there was a dedicated button without a "danger" label on the side of your PC that did so.juliocbcotta
03/09/2024, 7:37 PMjuliocbcotta
03/09/2024, 7:41 PMjuliocbcotta
03/09/2024, 7:43 PM