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

Repaired some of ListenableFuture.kt's cancellation corner cases. #1441

Merged
merged 1 commit into from Aug 29, 2019

Conversation

yorickhenning
Copy link
Contributor

This fixes:

  • Cancellation without an untrapped CancellationException propagating
    through a Callback; isCancelled() is the correct way to check for
    cancellation
  • Bidirectional propagation of cancellation through
    asListenableFuture()
  • The cause getting lost in the asListenableFuture() future when
    cancelling its Deferred parent with a cause

This also:

  • Extensively documents the package and the contracts created by the
    promise-creating extension methods and future()
  • Uses getUninterruptibly() for speed
  • Uses AbstractFuture to make as certain as possible that
    Future.cancel() will return true at most once
  • Should clear up rare spooky race conditions around
    cancellation/interruption in hybrid Coroutines/Guava Futures
    codebases

There are probably a few more interesting corner cases hiding in here,
but this should be a good start improving the correctness of .guava's
adapters.

This only addresses the asFoo() adapters, not the future() direct path. That also needs some cancellation changes. TBD.

This is a squash commit of kotlin/pr/1347 with some more improvements, rebased on develop:

  • Incorporated first-round feedback.

  • Merged CancellationToCoroutine into ListenableFutureCoroutine to save an
    allocation.

  • Documented and tested for null completion of asDeferred()'s parent
    Future.

  • Renamed a cancellation test case for clarity of purpose.

  • Split asDeferred() documentation between KDoc/details

  • Implemented InternalFutures faster-fast path. Documented.

This fixes:
  - Cancellation without an untrapped CancellationException propagating
    through a Callback; isCancelled() is the correct way to check for
    cancellation
  - Bidirectional propagation of cancellation through
    `asListenableFuture()`
  - The cause getting lost in the `asListenableFuture()` future  when
    cancelling its `Deferred` parent with a cause

This also:
  - Extensively documents the package and the contracts created by the
    promise-creating extension methods and `future()`
  - Uses `getUninterruptibly()` for speed
  - Uses `AbstractFuture` to make as certain as possible that
    `Future.cancel()` will return `true` at most once
  - Should clear up rare spooky race conditions around
    cancellation/interruption in hybrid Coroutines/Guava Futures
    codebases

There are probably a few more interesting corner cases hiding in here,
but this should be a good start improving the correctness of `.guava`'s
adapters.

This is a squash commit of kotlin/pr/1347, rebased on develop:

- Incorporated first-round feedback.

- Merged CancellationToCoroutine into ListenableFutureCoroutine to save an
allocation.

- Documented and tested for null completion of asDeferred()'s parent
Future.

- Renamed a cancellation test case for clarity of purpose.

- Split asDeferred() documentation between KDoc/details

- Implemented InternalFutures faster-fast path. Documented.
@yorickhenning
Copy link
Contributor Author

@qwwdfsad

@yorickhenning
Copy link
Contributor Author

Filed a followup issue so future() doesn't slip through unfixed.

@qwwdfsad qwwdfsad self-requested a review August 21, 2019 14:08
Copy link
Member

@qwwdfsad qwwdfsad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job!
Pushed some minor changes in 8c1b476, will merge soon

@elizarov elizarov merged commit 57cc364 into Kotlin:develop Aug 29, 2019
@@ -38,81 +47,98 @@ public fun <T> CoroutineScope.future(
): ListenableFuture<T> {
require(!start.isLazy) { "$start start is not supported" }
val newContext = newCoroutineContext(context)
// TODO: It'd be nice not to leak this SettableFuture reference, which is easily blind-cast.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nice short way to do this, if you like: return object: ListenableFuture<T> by future {}.

@elizarov elizarov mentioned this pull request Sep 4, 2019
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.

None yet

4 participants