-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Tweak CancellableContinuation.invokeOnCancellation contract for consistency #1915
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
Comments
This is a problematic for Android when Main dispatcher is cancelled on destroyed activity. Atomic nature of channels is designed to prevent loss of elements, which is really not an issue for a typical application, but creates problem when used with channels. * Internal suspendAtomicCancellableCoroutine -> suspendCancellableCoroutine * Internal suspendAtomicCancellableCoroutineReusable -> suspendCancellableCoroutineReusable * Remove atomic cancellation from docs * Ensures that flowOn does not resume downstream after cancellation. * MODE_ATOMIC_DEFAULT renamed into MODE_ATOMIC * Introduced MODE_CANCELLABLE_REUSABLE to track suspendCancellableCoroutineReusable * Better documentation for MODE_XXX constants. * Added stress test for proper handling of MODE_CANCELLABLE_REUSABLE and fixed test for #1123 bug with job.join (working in MODE_CANCELLABLE) that was not properly failing in the absence of the proper code in CancellableContinuationImpl.getResult * Added test for Flow.combine that should be fixed * Support extended invokeOnCancellation contract * Introduced internal tryResumeAtomic Fixes #1265 Fixes #1813 Fixes #1915
This is a problematic for Android when Main dispatcher is cancelled on destroyed activity. Atomic nature of channels is designed to prevent loss of elements, which is really not an issue for a typical application, but creates problem when used with channels. * Internal suspendAtomicCancellableCoroutine -> suspendCancellableCoroutine * Internal suspendAtomicCancellableCoroutineReusable -> suspendCancellableCoroutineReusable * Remove atomic cancellation from docs * Ensures that flowOn does not resume downstream after cancellation. * MODE_ATOMIC_DEFAULT renamed into MODE_ATOMIC * Introduced MODE_CANCELLABLE_REUSABLE to track suspendCancellableCoroutineReusable * Better documentation for MODE_XXX constants. * Added stress test for proper handling of MODE_CANCELLABLE_REUSABLE and fixed test for #1123 bug with job.join (working in MODE_CANCELLABLE) that was not properly failing in the absence of the proper code in CancellableContinuationImpl.getResult * Added test for Flow.combine that should be fixed * Support extended invokeOnCancellation contract * Introduced internal tryResumeAtomic Fixes #1265 Fixes #1813 Fixes #1915
This is a problematic for Android when Main dispatcher is cancelled on destroyed activity. Atomic nature of channels is designed to prevent loss of elements, which is really not an issue for a typical application, but creates problem when used with channels. * Internal suspendAtomicCancellableCoroutine -> suspendCancellableCoroutine * Internal suspendAtomicCancellableCoroutineReusable -> suspendCancellableCoroutineReusable * Remove atomic cancellation from docs * Ensures that flowOn does not resume downstream after cancellation. * MODE_ATOMIC_DEFAULT renamed into MODE_ATOMIC * Introduced MODE_CANCELLABLE_REUSABLE to track suspendCancellableCoroutineReusable * Better documentation for MODE_XXX constants. * Added stress test for proper handling of MODE_CANCELLABLE_REUSABLE and fixed test for #1123 bug with job.join (working in MODE_CANCELLABLE) that was not properly failing in the absence of the proper code in CancellableContinuationImpl.getResult * Added test for Flow.combine that should be fixed * Support extended invokeOnCancellation contract * Introduced internal tryResumeAtomic Fixes #1265 Fixes #1813 Fixes #1915
This is a problematic for Android when Main dispatcher is cancelled on destroyed activity. Atomic nature of channels is designed to prevent loss of elements, which is really not an issue for a typical application, but creates problem when used with channels. * Internal suspendAtomicCancellableCoroutine -> suspendCancellableCoroutine * Internal suspendAtomicCancellableCoroutineReusable -> suspendCancellableCoroutineReusable * Remove atomic cancellation from docs * Ensures that flowOn does not resume downstream after cancellation. * MODE_ATOMIC_DEFAULT renamed into MODE_ATOMIC * Introduced MODE_CANCELLABLE_REUSABLE to track suspendCancellableCoroutineReusable * Better documentation for MODE_XXX constants. * Added stress test for proper handling of MODE_CANCELLABLE_REUSABLE and fixed test for #1123 bug with job.join (working in MODE_CANCELLABLE) that was not properly failing in the absence of the proper code in CancellableContinuationImpl.getResult * Added test for Flow.combine that should be fixed * Support extended invokeOnCancellation contract * Introduced internal tryResumeAtomic Fixes #1265 Fixes #1813 Fixes #1915
It seems to me that the two APIs have different use-cases in most cases:
If the cancellation and resumption are done in separate threads, then they can race. This should not matter for the Since it can happen in race conditions anyway, I can see why it might seem a good idea to extend For example, the Glide image loading library in Android does not provide an API for cancelling a request directly; instead we can only cancel the ongoing request on a specific target view. Therefore cancellation can only be performed correctly while the request is ongoing, since performing the cancellation after completion might cause a different request to be cancelled. Since it's a UI library, it's bound to the main thread, and all calls, callbacks, and cancellations can only be done on the main thread. Due to this, there can be no race conditions in cancellation and resumption calls, so using While such cases are probably rare, cases like |
@1zaman Cancellation with coroutines is always asynchronous in nature and can be initiated by any thread. You cannot simply avoid it. The only way to ensure that cancellation and resumption are always performed on the same thread is to only use a single thread in your whole application, which is impractical limitation.
Note, that |
Hmm, this library can only be used from UI code, so both the scope cancellation and resumption will always be done from the main thread. However, I suppose there could also be edge cases like the coroutine being cancelled from another (child?) coroutine that was running in a separate thread, and therefore we should be prepared to handle cancellations from other threads as well. So I suppose we can conclude that even though this change might break some working implementations in rare cases, these cases wouldn't have been implemented correctly according to the documentation to begin with. In light of this, I'll remove my objections to this change. |
This is a problematic for Android when Main dispatcher is cancelled on destroyed activity. Atomic nature of channels is designed to prevent loss of elements, which is really not an issue for a typical application, but creates problem when used with channels. * Internal suspendAtomicCancellableCoroutine -> suspendCancellableCoroutine * Internal suspendAtomicCancellableCoroutineReusable -> suspendCancellableCoroutineReusable * Remove atomic cancellation from docs * Ensures that flowOn does not resume downstream after cancellation. * MODE_ATOMIC_DEFAULT renamed into MODE_ATOMIC * Introduced MODE_CANCELLABLE_REUSABLE to track suspendCancellableCoroutineReusable * Better documentation for MODE_XXX constants. * Added stress test for proper handling of MODE_CANCELLABLE_REUSABLE and fixed test for #1123 bug with job.join (working in MODE_CANCELLABLE) that was not properly failing in the absence of the proper code in CancellableContinuationImpl.getResult * Added test for Flow.combine that should be fixed * Support extended invokeOnCancellation contract * Introduced internal tryResumeAtomic Fixes #1265 Fixes #1813 Fixes #1915
This is a problematic for Android when Main dispatcher is cancelled on destroyed activity. Atomic nature of channels is designed to prevent loss of elements, which is really not an issue for a typical application, but creates problem when used with channels. * Internal suspendAtomicCancellableCoroutine -> suspendCancellableCoroutine * Internal suspendAtomicCancellableCoroutineReusable -> suspendCancellableCoroutineReusable * Remove atomic cancellation from docs * Ensures that flowOn does not resume downstream after cancellation. * MODE_ATOMIC_DEFAULT renamed into MODE_ATOMIC * Introduced MODE_CANCELLABLE_REUSABLE to track suspendCancellableCoroutineReusable * Better documentation for MODE_XXX constants. * Added stress test for proper handling of MODE_CANCELLABLE_REUSABLE and fixed test for #1123 bug with job.join (working in MODE_CANCELLABLE) that was not properly failing in the absence of the proper code in CancellableContinuationImpl.getResult * Added test for Flow.combine that should be fixed * Support extended invokeOnCancellation contract * Introduced internal tryResumeAtomic Fixes #1265 Fixes #1813 Fixes #1915
…otlin#1937) This is a problematic for Android when Main dispatcher is cancelled on destroyed activity. Atomic nature of channels is designed to prevent loss of elements, which is really not an issue for a typical application, but creates problem when used with channels. * Internal suspendAtomicCancellableCoroutine -> suspendCancellableCoroutine * Internal suspendAtomicCancellableCoroutineReusable -> suspendCancellableCoroutineReusable * Remove atomic cancellation from docs * Ensures that flowOn does not resume downstream after cancellation. * MODE_ATOMIC_DEFAULT renamed into MODE_ATOMIC * Introduced MODE_CANCELLABLE_REUSABLE to track suspendCancellableCoroutineReusable * Better documentation for MODE_XXX constants. * Added stress test for proper handling of MODE_CANCELLABLE_REUSABLE and fixed test for Kotlin#1123 bug with job.join (working in MODE_CANCELLABLE) that was not properly failing in the absence of the proper code in CancellableContinuationImpl.getResult * Added test for Flow.combine that should be fixed * Support extended invokeOnCancellation contract * Introduced internal tryResumeAtomic * Channel onUnderliveredElement is introduced as a replacement. Fixes Kotlin#1265 Fixes Kotlin#1813 Fixes Kotlin#1915 Fixes Kotlin#1936 Co-authored-by: Louis CAD <[email protected]> Co-authored-by: Vsevolod Tolstopyatov <[email protected]>
…otlin#1937) This is a problematic for Android when Main dispatcher is cancelled on destroyed activity. Atomic nature of channels is designed to prevent loss of elements, which is really not an issue for a typical application, but creates problem when used with channels. * Internal suspendAtomicCancellableCoroutine -> suspendCancellableCoroutine * Internal suspendAtomicCancellableCoroutineReusable -> suspendCancellableCoroutineReusable * Remove atomic cancellation from docs * Ensures that flowOn does not resume downstream after cancellation. * MODE_ATOMIC_DEFAULT renamed into MODE_ATOMIC * Introduced MODE_CANCELLABLE_REUSABLE to track suspendCancellableCoroutineReusable * Better documentation for MODE_XXX constants. * Added stress test for proper handling of MODE_CANCELLABLE_REUSABLE and fixed test for Kotlin#1123 bug with job.join (working in MODE_CANCELLABLE) that was not properly failing in the absence of the proper code in CancellableContinuationImpl.getResult * Added test for Flow.combine that should be fixed * Support extended invokeOnCancellation contract * Introduced internal tryResumeAtomic * Channel onUnderliveredElement is introduced as a replacement. Fixes Kotlin#1265 Fixes Kotlin#1813 Fixes Kotlin#1915 Fixes Kotlin#1936 Co-authored-by: Louis CAD <[email protected]> Co-authored-by: Vsevolod Tolstopyatov <[email protected]>
Consider the following code that uses suspendCancellableCoroutine:
This call to
suspendCancellableCoroutiune
will not necessarily produceresult == Unit
. It may also end up throwingCancellationException
. While indistinguishable from the outside, internally there are two conceptually different cases whenCancellationExcption
could be produced:cont.resume(Unit)
.cont.resume(Unit)
, but before the continuation gets dispatched to the caller.Now there are two ways to install a handler that gets invoked on cancellation:
cont.invokeOnCancellation { ... }
(stable) — continuation cancellation handler.cont.resume(Unit) { ... }
(experimental) — resume cancellation handler.They are currently not consistent between themselves with respect as to which cancellation cases they react to:
cont.invokeOnCancelltion { ... }
continuation handler only gets invoked in case A of cancellation. Ifresume
happened before cancellation, but the coroutine was canceled later (case B), then the continuation cancellation handler is not called.cont.resume(Unit) { ... }
cancellation handler gets invoked in both cases.The proposal is to make them consistent so that both are invoked in both cases. While this is technically a breaking change, it is not clear what kind of correctly working code this kind of change can break, since the difference in behavior would only manifest in cases when
cont.resume
was called after cancellation, but there is no way to figure out whethercancel
orresume
was first anyway.There is a motivation beyond consistency, too. This inconsistency turned out while working on #1813. When the prototype without atomic cancellation was implemented, the implementation of
Semaphore
stoped to work correctly in case ofacquire
cancellation. The root of the problem is that with atomic cancellation the case B was not possible and there was no need to worry about it. However, making the proposed change toinvokeOnCancellation
(so that it is called in case B) fixedSemaphore
. This leads us to believe that this change may also eliminate somecancel
/resume
races that currently exist in 3rd party code, too, because 3rd party code did not have access to atomic cancellation so it did not have any means to distinguish two cases of cancellation and was likely incorrect in racing scenarios.The text was updated successfully, but these errors were encountered: