Skip to content

Inconsistent cancellation and lost exceptions #333

Closed
@qwwdfsad

Description

@qwwdfsad

Consider following snippet:

val job = async() {
    barrier.await()
    throw IOException()
}

barrier.await()
job.cancel(TestException())
job.await() // <- 1

1 will always throw IOException and it will have eitherCompletedExceptionally or Cancelled state. TestException is always lost even when cancel call was successful.

Now slightly rewrite it:

val job = async() { 
   barrier.await()
   withContext(wrapperDispatcher(coroutineContext)) { // Just a wrapper to avoid fast paths in implementation
      throw IOException()
   }
}

barrier.await()
job.cancel(TestException())
job.await() // <- 2

Now 2 now can throw JobCancellationException: Job is being cancelled which definitely shouldn't be a terminal exception. Depending on timing it also may lose either TestException or IOException (even when its known that IOException was thrown).

What user may expect

  1. When it's known that IOException was thrown, await() should throw it as well. If concurrent cancellation with the cause was successful, IOException should have its cause as suppressed exception
  2. When it's known that IOException wasn't thrown (cancellation was "first"), await should throw TestException
  3. JobCancellationException should never be thrown if the cause is present on all code paths and the job is in its final state
  4. No intermediate JobCancellationException should be present in the final state of Job

We should design, rework and document exception handling mechanism.
We can give up some performance on an exceptional path (especially when multiple exceptions are thrown), but regular code path should stay the same

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions