-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Breaking: Rethink atomicity of certain low-level primitives #1813
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
It could help us understand the issue better if you had some tests to prove the old behavior's pitfalls, and some code proposals or tests that should pass with the fixes applied. |
@pakoito The pitfalls of atomic cancellation can be demonstrated with this code: import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
fun main() {
// The main scope that is similar to a typical UI application (the single main thread)
val mainScope = CoroutineScope(newSingleThreadContext("TheMainThread"))
val channel = Channel<String>(1) // buffered channel to make example simpler
// consumer coroutine
val consumer = mainScope.launch {
val ok = channel.receive()
check(ok == "OK") // this is fine
check(isActive) // WTF! The coroutine was cancelled, but we are still running here! FAILS HERE !!!
}
// producer coroutine
mainScope.launch {
channel.send("OK") // send to the channel
mainScope.cancel() // now the main scope is cancelled
}
// wait for test to run
runBlocking { consumer.join() }
} Try it in Kotlin Playground: https://pl.kotl.in/x5Bi-IiAO and see that it gets:
That is failing line 12 is With the proposed change the same code completes without errors because line 10 with |
Thank you for the detailed proposal. My humble opinion is: don't pollute the API and simply drop the atomic cancellation feature (Option 1), as you state this is a "confidential" feature. I don't know if you follow semantic versioning, but I would expect such change to appear in a major (ie. 2.0), making this change for a minor (1.4) could throw off the rare people using it. I suspect the versioning of For me the behavior of this feature is so unexpected that I don't expect a lot of people to mind the change if the "main" API stays the same and that you provide an alternative API for the use-case atomic cancellation was designed for. |
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
Update: A different, simpler, and more convenient replacement for safe transfer of resources via channels will be introduced. See #1936. |
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
While I agree with removing the atomic cancellation behaviour, it does lead to a highly undesirable and non-intuitive situation where an element that was successfully sent to a Many Another option is to implement the resending functionality in the Even if this is not implemented, this problem and the potential solutions should probably be at least explicitly mentioned in the |
Just my 2 cents: |
Following up on my previous comment, I have realized that there's also a way for This can be done if all receiver dispatches are performed sequentially, by suspending all other receivers while there's any ongoing dispatch to a receiver. Then if the ongoing dispatch is cancelled, the Since this would allow a single slow dispatcher of a receiver to completely halt all other receivers, there could also be a receiver dispatching timeout implemented, after which the dispatcher would be cancelled by the |
@1zaman When you say "many users" do you have a specific use-case in mind? What kind of application might need it? Can you elaborate on this scenario, please. |
@pacher First of all we need to find those "advanced folks" who had relied on this underdocumented behavior to actually implement a correct code and figure out what was their use of atomic cancellation. Do you have any specific examples in mind? |
@elizarov No, unfortunately not. I am not affected by this change, but I could have been. Trying to image myself in this situation and my suggestion is what would suit me, personally, the best. Read release notes - find and replace - deal with it later. As for finding "advanced folks", we can't expect everybody reading every issue in the repo, but it is reasonable to expect people to read release notes and migration guide for at least "major" version bump. Hence the suggestion. |
@elizarov: Let's consider for example an Android app which receives push notifications, and wants to have them displayed as a dialog inside the app. This can be done by creating a Now if a new To keep the case simple, I left out a fallback for handling the notifications when the app is in the background, which in most cases would be implemented by having them shown in Android's notification drawer. This could be achieved by adding a coroutine to receive and handle the notifications when the app is in the background, which would encounter the same issues mentioned before. I didn't include this in the main scenario, because there could also be other ways to implement a fallback, such as by using |
@1zaman I don't think |
@LouisCAD: I don't need the sharing/broadcasting feature provided by Note that this problem will be present in any scenario where scoped or cancellable coroutines receive from a |
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]>
Sorry, I know this ship has sailed, but I'd like to leave a note. Atomic cancellation was the most important differentiating feature between Coroutines and other process concurrency libraries I've used. I rather liked it. Some of my code got broken when 1.4.X rolled out to me. I understood atomic cancellation and send/receive under cancellation, and I used it to write in-order responses from sequentially-started concurrently-executing hedged requests, without paying This is a toy version of the algorithm using a
Under "prompt cancellation" and given that
I've tried 1.3.3 and 1.3.9 and The real Anyway, with the breaking change to cancellation at My understanding of "prompt cancellation" is still related to happens-before, and As implemented now, cancellation is racy in a way that suggests that cancellation is a best-effort check at barriers. I've read My mental model of
My new mental model is more like:
That might be faster sometimes, but if I need to trade in relaxed cancellation checking at a hot lock, I can write a fast path. As the default, I found deterministic cancellation the better choice for correctness. It allowed unit tests (using real pool Dispatchers, not the undispatched single-threaded test Dispatcher) to be fairly sure there was no low-probability race unique to cancellation cases. Now, only load tests are likely to find those cancellation corner cases. With the new cancellation model, if I want to avoid "oops kept running after cancellation" bugs after entering a shared critical section, I can check the It'd be good if coroutines' atomicity model didn't change between versions without an API break. Either accidentally (like I think |
@yorickhenning Thank you for the feedback. It was a tough decision to make, but maintaining the old atomic cancellation behavior was just becoming too cumbersome. I'm surprised you've found it different from other concurrent primitives because the difference is only apparent in the presence of cancellation, which is a pretty unique feature of Kotlin coroutines itself. Without cancellation, it all just works similarly between different languages and different libraries. However, I've looked at your original code and I don't see how its behavior is related to atomic cancellation. Since version 1.0 coroutines primitives were checking for cancellation when they were suspended. In particular, So, it was always possible to have [2, 1], because the second producer might be simply too late to cancel the first one. The cancellation may come at a moment when the 1st producer just finished doing In order to prevent [2, 1] you'll need to add |
I have sympathy for that. Synchronization points have clear tradeoffs against low-level performance. Atomic cancellation meant synchronization.
I tried to give a simple example of why I might care about deterministic cancellation checkpointing in a concurrent algorithm. I should've stuck to the specific example... But I'm now unconvinced it didn't rely on implementation details. :) A rendezvous
Thanks for confirming that I found the prior Mutex documentation ambiguous about suspension timing. The newer
The systems I work on care a lot about efficient cancellation, backpressure, and keeping ACID under interrupt-style cancellation of process pipelines. Interruption-to-the-socket, bidirectional streaming, lots of user-defined concurrent functions. So, the difference is highly apparent in the way I use coroutines. And a contrast to other languages and libraries I've used to do the same things. A Java I'm not certain whether Coroutines' cancellation changes make any properties harder to implement, but I sure depended on how cancellation worked. The observability of a system's behaviour is its public API, and all? I should've read the patch notes. :) I'm not sure I've discovered everything cancellation changes impacted, yet. This new """
Yep, that's exactly how that section reads, now. |
I suppose these cancellation subtleties deserve to be mentioned in the "Shared flows, broadcast channels" article, which suggests using channels to "handle events that must be processed exactly once", because "posted events are never dropped by default". Some of us took this literally and eventually got issues. |
Good point, added. |
…it handled exactly once. Together with `Store` it can also guarantee delivery of the side effect. Also see: [Proposal] Primitive or Channel that guarantees the delivery and processing of items Kotlin/kotlinx.coroutines#2886 Rethink atomicity of certain low-level primitives Kotlin/kotlinx.coroutines#1813 LiveData with SnackBar, Navigation and other events (the SingleLiveEvent case) https://medium.com/androiddevelopers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150 https://gist.github.com/JoseAlcerreca/5b661f1800e1e654f07cc54fe87441af Shared flows, broadcast channels https://elizarov.medium.com/shared-flows-broadcast-channels-899b675e805c https://gmk57.medium.com/unfortunately-events-may-be-dropped-if-channel-receiveasflow-cfe78ae29004 https://gist.github.com/gmk57/330a7d214f5d710811c6b5ce27ceedaa Signed-off-by: Artyom Shendrik <[email protected]>
…it handled exactly once. Together with `Store` it can also guarantee delivery of the side effect. Also see: [Proposal] Primitive or Channel that guarantees the delivery and processing of items Kotlin/kotlinx.coroutines#2886 Rethink atomicity of certain low-level primitives Kotlin/kotlinx.coroutines#1813 LiveData with SnackBar, Navigation and other events (the SingleLiveEvent case) https://medium.com/androiddevelopers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150 https://gist.github.com/JoseAlcerreca/5b661f1800e1e654f07cc54fe87441af Shared flows, broadcast channels https://elizarov.medium.com/shared-flows-broadcast-channels-899b675e805c https://gmk57.medium.com/unfortunately-events-may-be-dropped-if-channel-receiveasflow-cfe78ae29004 https://gist.github.com/gmk57/330a7d214f5d710811c6b5ce27ceedaa Signed-off-by: Artyom Shendrik <[email protected]>
There is a number of primitives in the library that provide "atomic cancellation":
As their documentation states:
This is a surprising behavior that needs to be accounted for when writing higher-level primitives like
Flow
. See #1265 as an example problem and discussion in #1730.It all points to the need to rethink the atomic cancellation behavior of the underlying primitives.
More background on the atomic cancellation
The concept of atomic cancellation has been introduced early in the design of
kotlinx.coroutines
, before1.0
release. Originally it was used only in channels with the goal of enabling safe ownership transfer of closeable resources between different coroutines.For example, consider a coroutine that opens a file and gets an
InputStream
.InputStream
is a resource that must be explicitly closed after use. Now, if the coroutine needs to send it to another coroutine for processing viaval channel = Channel<InputStream>()
, then it has the following code:the other coroutine receives the input stream and continues to work with it, closing it afterward:
If
send
andreceive
were simply cancellable, as most other suspending functions created with the help of suspendCancellableCoroutine are, then the sender would have no way of knowing if thesend
had actually completed successfully or not. It could be canceled before completing send or it could send and then get canceled. The receiver can get canceled after receiving it, too. There would be no way to ensure safe ownership transfer from one coroutine to another. As a solution to this problem the concept of "atomic cancellation" was born. Inkotlinx.coroutines
up until now, the above code is given the following guarantees:send
completes successfully then the reference was put into the channel, otherwise it was not.receive
completes successfully then the reference was retrieved from the channel, otherwise it was not.Together, these two guarantees support the leak-free transfer of ownership between coroutines when the code on the send side modified in a specific way like this:
Public API status of atomic cancellation
The "atomic cancellation" was never widely publicized. It was briefly documented by a single paragraph in both
send
andreceive
documentation, but it was not mentioned in any of the guides concerning coroutines. Moreover, as it was shown above, the atomic cancellation itself only enables safe ownership transfer. One still has to write the code with extreme caution to ensure that resources are transferred safely between coroutines and the corresponding code one has to write was never explained in the documentation before.The atomic cancellation was only employed by a small number of
kotlinx.coroutines
functions and it was not possible to write user-defined primitives with similar atomic-cancellation behavior. The corresponding low-level mechanisms are internal in the library.Problems with atomic cancellation
Atomic cancellation creates a major hurdle when using channels in UI code. In UI application cancellation is used to manage the lifecycle of UI elements. For example, consider a coroutine that is running in the scope of some UI view:
When the
data
is received from the channel the line withupdateUI(data)
is not executed immediately. The corresponding continuation is scheduled for execution onto the main thread and needs to wait until the main thread is available. However, while this continuation waits in the queue the corresponding UI view might get destroyed. Normally, with all the other cancellable suspending functions that usesuspendCancellableCoroutines
, when the main thread is finally ready to execute the continuation the cancellation state of the coroutine is checked and theCancellationException
is thrown instead of executingupdateUI(data)
. However, this check for cancellation is not performed forChannel.receive
continuation because there is an "atomic cancellation" guarantee forreceive
. The data element was already received and must be delivered, soupdateUI(data)
will get executed despite the fact that the coroutine is already canceled. On Android, in particular, an attempt to update UI view that was already destroyed would lead to an exception.In practice, it means that every time a
Channel
(or another atomically-cancellable primitive) is used in the main thread, one must not forget to manually add the check for cancellation of the current coroutine:Forgetting to add this check leads to hard-to-find bug that only rarely manifests itself.
This design creates irregularities in the coroutines API surface. All suspending functions in
kotlinx.coroutines
are cancellable in a regular fashion and it is guaranteed that when they resume successfully the corresponding coroutine was not canceled. However, there are a few exceptional functions with "atomic cancellation" behavior that one must remember by heart. There is no consistent naming to make them stand apart. They look like all the other suspending functions and they are cancellable, too, but they can resume successfully when the coroutine was already cancellated. Tricky and error-prone.Moreover, atomic cancellation does not make resource transfer easy. Even with atomic cancellation, it is still a tricky and error-prone endeavor. In addition to the intricate code-dance, one has to perform (as shown above), there is a problem with channel cancellation. When the receiver on the channel does not plan to continue receiving data and cancels the whole channel to indicate that, all references that were stored in the channel buffer are simply dropped. It means that channel cancellation cannot be used when channel is used to transfer resources. You can work around it when you use a channel directly (avoid cancellation, manually retrieve and close all resources from the channel you no longer need), but this makes it impossible to design resource-leak-free high-level primitives like Kotlin
Flow
that must rely on channel cancellation for their own needs.All in all, atomic cancellation fails to deliver on the promise of being a safe solution for resource transfer from one coroutine to another.
Additional details on Mutex and Semaphore
The atomic cancellation of
Mutex.lock
andSemaphore.acquire
does not bring any advantages as with channels, but only all the problems. The typical pattern of mutex/semaphore use is:The correctness of this pattern does not require that
lock
is atomically cancellable in the same way assend
. All it needs is that a canceledlock
attempt does not keep the mutex locked.CancellableContinuation
already providesresume(value) { doOnCancel() }
API to enable a correct lock implementation. See resume.Proposed solution
The proposal is to completely drop the concept of "atomic cancellation" from
kotlinx.coroutines
and make all the suspending functions in the library to be consistently cancellable in a regular way with a guarantee that a suspending function does not resume normally in a canceled coroutine.In order to address the rare use-case of actually sending resources between coroutines via channels a different easy-to-use mechanism will be introduced together with this change. See #1936 for a concrete proposal.
The key rationale for this particular and radical way to address the problem is that atomic cancellation was underdocumented and very error-prone to start with. Thus, it is extremely unlikely that there is a significant amount of code in the wild that correctly exploits those atomic cancellation guarantees to perform a leak-free transfer of resources between coroutines. At the same time, changing the behavior to regular cancellation will likely fix a lot of hard-to-find cancellation bugs in the existing Android applications that use coroutines.
Impact of the change
This is breaking change. Some code might have been relying on the atomic cancellation guarantees. After this change this code might encounter two kinds of errors:
CancellationException
fromchannel.send
the code might assume that item was not sent to the channel and will go to close the corresponding resource. However, in fact, without atomic cancellation guarantee onsend
, the resource might have been actually delivered to the receiver.receive
call for a resource what was previously successfully sent channel to the channel might getCancellationException
without atomic cancellation guarantee onchannel.receive
. Thus, the reference is lost and and the resource leaks.Whether this is an acceptable behavior change for a major release is TBD. The key question is whether there are any widely used libraries that could be seriously impacted by this change. We plan to publish at least one
-Mx
(milestone) release with the proposed change so that the impact of this change could be evaluted while running the actual code with the updatedkotlinx.coroutines
.Possible alternatives
We can try to maintain a certain degree of backward compatibility while making this change:
Option 1: Maintain binary backward compatibility. It means that we'll retain the old behavior (with atomic cancellation) for previously compiled code but any code that gets compiled against new version of
kotlinx.compatibility
will get new behavior (without atomic cancellation).Option 2: Maintain source backward compatibility. It means that old
send
andreceive
will behave as before (with atomic cancellation) and some new methods to perform regularly cancellable send and receive are introduced. It can be done by either having a dedicatedChannel
constructor to customize cancellation behavior or dedicatedsend
/receive
methods with new names.kotlinx.coroutines
more complex. What's worse, as was shown in the preceding overview, the whole atomic cancellation behavior is hard-to-use and error-prone anyway. It does not deserve to be maintained in the long run, so the corresponding "old" methods that perform atomically-cancellablesend
/receive
will have to be deprecated, meaning a transition to less natural names.The text was updated successfully, but these errors were encountered: