Skip to content

Add cooperativeCatch as coroutine-safe alternative to runCatching #4059

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from

Conversation

mattshoe
Copy link

@mattshoe mattshoe commented Mar 8, 2024

What is this?

This PR adds 2 new methods:

  1. cooperativeCatch
  2. cooperativeMap

Both of these function the same way that runCatching and mapCatching.
However, these implementations obey cooperative cancellation by immediately propagating CancellationException instead of encapsulating it, as runCatching and mapCatching do.

Why do we want it?

Firstly, it is not immediately obvious to developers that runCatching may actually swallow CancellationException and cause memory leaks when a suspend function is invoked inside runCatching. It is perfectly reasonable to assume that devs would think a language-provided feature would play nicely with another language-provided feature

Secondly, many devs strongly prefer the functional style of runCatching over a typical try/catch block. And they simply cannot use their preferred functional style of exception handling with the current state of coroutines. This PR gives devs the power to preserve their preferred programming style.

Discussion on this topic can be found in this issue: #1814

@mattshoe mattshoe changed the base branch from master to develop March 8, 2024 01:35
@dkhalanskyjb
Copy link
Collaborator

Firstly, it is not immediately obvious to developers that runCatching may actually swallow CancellationException

And this PR doesn't help with that. You still have to know about the new functions to invoke them.

Secondly, many devs strongly prefer the functional style of runCatching over a typical try/catch block.

runCatching is only occasionally useful, regardless of the preferred style, because in Java's (and, by proxy, Kotlin's) model of error handling, unexpected errors manifest themselves not as runtime panics (Rust- or Haskell-style) but as exceptions. runCatching catches an out-of-memory exception just as gladly as it catches an exception that can be meaningfully handled. In the try-catch style, this means that catching a Throwable is a huge anti-pattern, only to be done during foundational work. Using runCatching because of "style" falls in the same category. Please see https://elizarov.medium.com/kotlin-and-exceptions-8062f589d07 for an elaborate description of error handling in Kotlin.

Unfortunately, this problem is especially acute with CancellationException: in most cases, you must rethrow it, but in some cases, you must process it as you would any other. See #1814 (comment) for an explanation. Without clearing up the underlying conceptual issues first, runCatching-like functions with special behavior around CancellationException will only add to the confusion. And if someone wants to have these wrappers even despite the fact that they are error-prone in some cases, then they can introduce them in their own code, as they are straightforward to write.

In short, these wrappers only hide some of the complexity, but don't help to deal with it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants