question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Async builder and cancellation in structured concurrency

See original GitHub issue

Background

After async integration with structured concurrency (#552), any failure in async cancels its parent. For example, following code

try {
  async { error("") }.await()
} catch (e: Throwable) {
  ...
}

cancels outer scope and it usually leads to undesired consequences. The rationale behind this change was simple, it was previously hard or impossible to have proper parallel decomposition to fail when one of the decomposed computations failed.

While there is nothing wrong with that reasoning (=> we won’t revert this change), we now have a more serious problem with user’s intuition of this behaviour. async is a way too polluted keyword which neither reflects its purpose in kotlinx.coroutines nor matches similar concepts in other paradigms or programming languages, so any newcomer will be confused with this behaviour and thus has no or incorrect mental model about it.

Moreover, if someone already understands concept of kotlinx.coroutines async, (s)he still may want to have future-like behaviour (and according to our slack, demand for that is quite high). And there is no simple answer to that. To have a proper semantic (one-way cancellation), one should write something like async(SupervisorJob(coroutineContext[Job])) { ... } (really?!) and it completely defies the goal of having clear and easily understandable API. coroutineScope is not applicable for that purpose, because it awaits all its children and GlobalScope is just unsafe.

We should address this issue and educating users with “this is intended ‘async’ behaviour” without providing an alternative is just wrong. I can’t imagine the situation where someone asks about this behaviour in Slack and community responds with “yes, this is how it works, you actually need async(SupervisorJob(coroutineContext[Job])) { ... }

Possible solutions

In my opinion, it is necessary to provide future-like builder (aka “old” async) and it would be nice if its name won’t clash with anything we already have. For example, deferred builder. It is not something newcomers will start to use immediately (while async is kinda red flag “please use me”) due to its name, but it is a simple concept, it is clean, short and easy to explain (see my “can’t imagine the situation” rant).

Another possible solution (please take it with a grain of salt, it requires a lot more design/discussing with other users) is to deprecate async at all. As I have mentioned, this name is useless, polluted and does not reflect its semantics. Even with deferred builder, we still should make a tremendous effort with educating users that this is not async you are familiar with, this is completely different async. But if we will deprecate it and introduce another primitive with a more intuitive name, for example, decompose {} (naming here is a crucial part, decompose is just the first thing that popped in my mind), then we will have no problems with async. Newcomers won’t see their familiar async, but deferred and decompose and then will choose the right primitive consciously.

Reported: https://discuss.kotlinlang.org/t/caught-exceptions-are-still-propagated-to-the-thread-uncaughtexceptionhandler/10170 #753 #787 #691

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Reactions:43
  • Comments:47 (31 by maintainers)

github_iconTop GitHub Comments

7reactions
spyro2000commented, Apr 17, 2020

This is so confusing. I am reading and testing Kotlin async exception handling for days now. And I am still not getting it. This is even worse than Java with it’s Future-API from hell (can’t believe, I’m writing this).

Wish we could just have simple async/await equivalents like in C# or Javascript. They just do their stuff like expected without having to deal with (global) scopes, coroutine builders, dispatchers, suspend functions, supervisors, catched exceptions bubbling up and crashing my app etc.

The current state is just - awful. Everyone is just confused how to use all those stuff correctly. Sorry. In C# all those works with async/await like expected, done.

In Kotlin it’s rocket science.

5reactions
LouisCADcommented, Oct 27, 2018

@fvasco I don’t agree with making launch top level again without scope, there’s a reason it moved to structured concurrency with mandatory scopes. You can still write a globalLaunch or alike method if you want, but scopes are really useful for launch, think Android apps, child coroutines that should not be forgotten, etc…

For an async alternative though, like fork or something, it would be great. Then, we could have an async or alike that uses a SupervisorJobso it doesn't cancel parent on failure, but only throws whenawait()` is called.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Structured Concurrency in action! (using Kotlin coroutines)
“Structured concurrency” refers to a way to structure async/concurrent computations so that child operations are guaranteed to complete before ...
Read more >
Structured Concurrency (async let) - Cancellation
The task model is cooperative and it's guaranteed that a task will not end before any of its child tasks have completed first....
Read more >
Async Cancellation I — 2021-11-10
This wrapper can be adapted to work for async-std as well, and ensures that cancellation propagates across task bounds. Structured concurrency.
Read more >
Coroutines (Part III) – Structured Concurrency and ...
CoroutineExceptionHandler has no effect on async . A coroutine that was created using async always catches all its exceptions and stores them in ......
Read more >
Structured concurrency – available from Swift 5.5
SE-0304 introduced a whole range of approaches to execute, cancel, and monitor concurrent operations in Swift, and builds upon the work introduced by...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found