Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ private object IOFiberConstants {
final val UncancelableK = 7
final val UnmaskK = 8
final val AttemptK = 9
final val OnCancelRequestedK = 10

// resume ids
final val ExecR = 0
Expand Down
2 changes: 1 addition & 1 deletion core/js/src/main/scala/cats/effect/IOApp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ trait IOApp {
case Left(Outcome.Errored(t)) => IO.raiseError(t)
case Left(Outcome.Succeeded(code)) => code
case Right(Outcome.Errored(t)) => IO.raiseError(t)
case Right(_) => sys.error("impossible")
case Right(_) => IO.delay(sys.error("impossible"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this statement is not true anymore and reachable.

survives only because shouldFinalize() discards the suspended delay first

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If needed I can spend time crafting the test - I may be wrong.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It's been a while since I looked at this, but iirc, it ends up being canceled before the sys.error executes.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please look into test
reardonj#2

}
.unsafeRunFiber(
hardExit(cancelCode),
Expand Down
1 change: 1 addition & 0 deletions core/jvm/src/main/java/cats/effect/IOFiberConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ final class IOFiberConstants {
static final byte UncancelableK = 7;
static final byte UnmaskK = 8;
static final byte AttemptK = 9;
static final byte OnCancelRequestedK = 10;

// resume ids
static final byte ExecR = 0;
Expand Down
34 changes: 34 additions & 0 deletions core/shared/src/main/scala/cats/effect/IO.scala
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,19 @@ sealed abstract class IO[+A] private () extends IOPlatform[A] {
def cancelable(fin: IO[Unit]): IO[A] =
Spawn[IO].cancelable(this, fin)

/**
* Run the given effect when cancelation is requested. Unlike [[onCancel]], this will run
* before cancelation is observed, on a separate fiber, and will always allow `fa` to complete
* before cancelation is observed.
*
* @param ack
* an effect which orchestrates some external state which terminates `fa`
* @see
* [[onCancel]]
*/
def onCancelRequested(ack: IO[Unit]): IO[A] =
IO.OnCancelRequested(this, ack)

def forceR[B](that: IO[B]): IO[B] =
// cast is needed here to trick the compiler into avoiding the IO[Any]
asInstanceOf[IO[Unit]].handleError(_ => ()).productR(that)
Expand Down Expand Up @@ -2059,6 +2072,23 @@ object IO extends IOCompanionPlatform with IOLowPriorityImplicits with TuplePara
def onCancel[A](ioa: IO[A], fin: IO[Unit]): IO[A] =
ioa.onCancel(fin)

/**
* Run the given effect when cancelation is requested. Unlike [[onCancel]], this will run
* before cancelation is observed, on a separate fiber, and will always allow `fa` to
* complete before cancelation is observed.
*
* @param fa
* the effect to be canceled
* @param ack
* an effect which orchestrates some external state which terminates `fa`
* @see
* [[cancelable]]
* @see
* [[onCancel]]
*/
override def onCancelRequested[A](fa: IO[A], ack: IO[Unit]): IO[A] =
fa.onCancelRequested(ack)

override def bracketFull[A, B](acquire: Poll[IO] => IO[A])(use: A => IO[B])(
release: (A, OutcomeIO[B]) => IO[Unit]): IO[B] =
IO.bracketFull(acquire)(use)(release)
Expand Down Expand Up @@ -2328,6 +2358,10 @@ object IO extends IOCompanionPlatform with IOLowPriorityImplicits with TuplePara
def tag = 24
}

private[effect] final case class OnCancelRequested[A](f: IO[A], ack: IO[Unit]) extends IO[A] {
def tag = 25
}

// INTERNAL, only created by the runloop itself as the terminal state of several operations
private[effect] case object EndFiber extends IO[Nothing] {
def tag = -1
Expand Down
Loading
Loading