Skip to content
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

Reconsider what happens when await() gets cancelled #4329

Open
dkhalanskyjb opened this issue Jan 14, 2025 · 0 comments
Open

Reconsider what happens when await() gets cancelled #4329

dkhalanskyjb opened this issue Jan 14, 2025 · 0 comments
Labels

Comments

@dkhalanskyjb
Copy link
Collaborator

In kotlinx.coroutines, we have two types of functions called await():

A way to share (for example) a ListenableFuture among several consumers is to convert it to a Deferred and call await() on that.

There are a few things that bother me about this.

  • future.await() behaves differently from future.asDeferred().await(): one cancels the computation when await() is cancelled, the other one doesn't. It's inconsistent, and not in a way that's unavoidable when translating concepts across ecosystems: if Future.get gets interrupted, the whole computation doesn't get aborted, so this seems like kotlinx.coroutines invention.
  • Why do these two operations that behave significantly differently share the same name? The data structure that is used is orthogonal to whether it's single-shot. If the second kind of await() was called something different (for example, consume()), we could
    • For things that currently only support consuming, support the non-obvious step of first converting it to Deferred.
    • For things that currently only support awaiting, have what acts as a limited version of intersections of coroutine scopes. The latter is a wider issue than just cancelling an operation if one of two parents gets cancelled, but the idea of having a Deferred fail if either the only consumer fails or the component computing the value does seems like a notable special case.

https://youtrack.jetbrains.com/issue/KTIJ-17464/Warn-on-unstructured-no-CoroutineScope-protection-gap-between-getting-a-future-and-await-call-on-it also mentions that for the consuming await(), this pattern is incorrect:

val future = foo()
something() // can throw
future.await() // .consume()

If await() is never called, the computation leaks.

This leads to another problem that I think is worth considering: future.asDeferred() does not allow integrating into structured concurrency. If it did, then this problem would be avoided using this cleaner API:

val future = foo().asDeferred(currentCoroutineContext().job)
future.await()

The IDE inspection would then be limited to suggesting to pass something to asDeferred when it's called in a suspend context.

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

No branches or pull requests

1 participant