Skip to content

Async cancellation with structured concurrency #552

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
qwwdfsad opened this issue Sep 12, 2018 · 1 comment
Closed

Async cancellation with structured concurrency #552

qwwdfsad opened this issue Sep 12, 2018 · 1 comment

Comments

@qwwdfsad
Copy link
Collaborator

qwwdfsad commented Sep 12, 2018

Consider following code:

suspend fun foo() = coroutineScope<Unit> {
  val one = async {
    delay(Int.MAX_VALUE) // suspend until cancellation
  }

  val two = async { throw MyException() }

  awaitAll(one, two)
}

fun main(args: Array<String>) {
  runBlocking {
    try {
      foo()
    } catch (e: MyException) {
      // expected exception
    }
  }
}

This code works as expected, but if one replaces awaitAll(one, two) with one.await(); two.await() (e.g. to aggregate computed results), this code hangs forever.
A cause of this hang is clear: async does not cancel its parent on a failure.

But unconditionally cancelling parent may be undesired:

try {
  Scope.async { throw MyException() }.await()
} catch (e: MyException) {
  println("Exception caught")
}

Now whether "Exception caught" is printed depends on the Scope object: for GlobalScope it will be printed and for CoroutineScope(Job()) it will be not, which may lead to uncaught errors and user's confusion.

Related to #220 and #515

@SolomonSun2010
Copy link

For a long term evolution, I propose to support supervisor strategy with Akka or Erlang style, it's proofed.
Also I propose Kotlin support the argument reference,similar Java 8 method reference,or limited implicit parameters in Scala(implicit parameters is good parts in past years).
Both described as comments there:
https://medium.com/@elizarov/structured-concurrency-722d765aa952

elizarov added a commit that referenced this issue Sep 27, 2018
* Affects async, CompletableDeferred, and all Rx integration builders.
* This makes all coroutine builders totally consistent. They all
  cancel parent on failure, but they all consider "CancellationException"
  to be the case of "normal cancellation" that does not propagate to
  parent. The only missing case is Job() that should be fixed together
  with introduction of SupervisorJob().
* Note that "scoping" builders don't "cancel the parent", but rethrow
  the corresponding exception instead, so it that is how it gets
  propagated up the stack.
* This makes parallel decomposition exception-safe. You
  cannot loose an exception as along as default (child async)
  behavior is not overridden.

Fixes #552
elizarov added a commit that referenced this issue Sep 27, 2018
* Affects async, CompletableDeferred, and all Rx integration builders.
* This makes all coroutine builders totally consistent. They all
  cancel parent on failure, but they all consider "CancellationException"
  to be the case of "normal cancellation" that does not propagate to
  parent. The only missing case is Job() that should be fixed together
  with introduction of SupervisorJob().
* Note that "scoping" builders don't "cancel the parent", but rethrow
  the corresponding exception instead, so it that is how it gets
  propagated up the stack.
* This makes parallel decomposition exception-safe. You
  cannot loose an exception as along as default (child async)
  behavior is not overridden.

Fixes #552
elizarov added a commit that referenced this issue Sep 27, 2018
* Affects async, CompletableDeferred, and all Rx integration builders.
* This makes all coroutine builders totally consistent. They all
  cancel parent on failure, but they all consider "CancellationException"
  to be the case of "normal cancellation" that does not propagate to
  parent. The only missing case is Job() that should be fixed together
  with introduction of SupervisorJob().
* Note that "scoping" builders don't "cancel the parent", but rethrow
  the corresponding exception instead, so it that is how it gets
  propagated up the stack.
* This makes parallel decomposition exception-safe. You
  cannot loose an exception as along as default (child async)
  behavior is not overridden.

Fixes #552
elizarov added a commit that referenced this issue Sep 27, 2018
* Affects async, CompletableDeferred, and all Rx integration builders.
* This makes all coroutine builders totally consistent. They all
  cancel parent on failure, but they all consider "CancellationException"
  to be the case of "normal cancellation" that does not propagate to
  parent. The only missing case is Job() that should be fixed together
  with introduction of SupervisorJob().
* Note that "scoping" builders don't "cancel the parent", but rethrow
  the corresponding exception instead, so it that is how it gets
  propagated up the stack.
* This makes parallel decomposition exception-safe. You
  cannot loose an exception as along as default (child async)
  behavior is not overridden.

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

No branches or pull requests

2 participants