Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/shared/src/main/scala/cats/effect/IO.scala
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ sealed abstract class IO[+A] private () extends IOPlatform[A] {
* fiber has fully terminated.
*
* If the `IO` self-cancels and the `cancelable` itself is uncancelable, the resulting fiber
* will be equal to `never` (similar to [[race]]. Under normal circumstances, if `IO`
* will be equal to `never` (similar to [[race]]). Under normal circumstances, if `IO`
* self-cancels, that cancelation will be propagated to the calling context.
*
* @param fin
Expand Down
2 changes: 2 additions & 0 deletions docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,4 +164,6 @@ IO(new AtomicBoolean(false)) flatMap { flag =>

This is *almost* the same as the previous snippet except for the introduction of an `AtomicBoolean` which is checked on each iteration of the `while` loop. We then set this flag by using `cancelable` (right at the end), which makes the whole thing (safely!) cancelable, despite the fact that it was defined with `blocking` (note this also works with `delay` and absolutely everything else).

It is still worth keeping in mind that this is only a partial solution. Whenever `fis.read()` is blocked, cancelation will wait for that blocking to complete regardless of how long it takes. In other words, we still can't interrupt the `read()` function any more than we could with `interruptible`. All that `cancelable` is achieving here is allowing us to encode a more bespoke cancelation protocol for a particular effect, in this case in terms of `AtomicBoolean`.
Comment thread
djspiewak marked this conversation as resolved.

The reason this is safe is it effectively leans entirely on *cooperative* cancelation. It's relatively common to have effects which cannot be canceled by normal means (and thus are, correctly, `uncancelable`) but which *can* be terminated early by using some ancillary protocol (in this case, an `AtomicBoolean`). Note that nothing is magic here and this is still fully safe with respect to backpressure and other finalizers. For example, `fis.close()` will still be run at the proper time, and cancelation of this fiber will only complete when all finalizers are done, exactly the same as non-`uncancelable` effects.
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,8 @@ trait GenSpawn[F[_], E] extends MonadCancel[F, E] with Unique[F] {
* fiber has fully terminated.
*
* If `fa` self-cancels and the `cancelable` itself is uncancelable, the resulting fiber will
* be equal to `never` (similar to [[race]]. Under normal circumstances, if `fa` self-cancels,
* that cancelation will be propagated to the calling context.
* be equal to `never` (similar to [[race]]). Under normal circumstances, if `fa`
* self-cancels, that cancelation will be propagated to the calling context.
*
* @param fa
* the effect to be canceled
Expand Down