diff --git a/kernel/shared/src/main/scala/cats/effect/kernel/Resource.scala b/kernel/shared/src/main/scala/cats/effect/kernel/Resource.scala index b0614eda9f..f29fb5f03f 100644 --- a/kernel/shared/src/main/scala/cats/effect/kernel/Resource.scala +++ b/kernel/shared/src/main/scala/cats/effect/kernel/Resource.scala @@ -161,38 +161,41 @@ sealed abstract class Resource[F[_], +A] extends Serializable { case object Nil extends Stack[A] final case class Frame[AA, BB](head: AA => Resource[F, BB], tail: Stack[BB]) extends Stack[AA] + F.uncancelable { poll => + // Indirection for calling `loop` needed because `loop` must be @tailrec + def continue[C](current: Resource[F, C], stack: Stack[C]): F[B] = + loop(current, stack) + + // Interpreter that knows how to evaluate a Resource data structure; + // Maintains its own stack for dealing with Bind chains + @tailrec def loop[C](current: Resource[F, C], stack: Stack[C]): F[B] = + current match { + case Allocate(resource) => + F.bracketFull(p => p(resource(poll))) { + case (a, _) => + stack match { + case Nil => poll(onOutput(a)) + case Frame(head, tail) => continue(head(a), tail) + } + } { + case ((_, release), outcome) => + onRelease(release, ExitCase.fromOutcome(outcome)) + } + case Bind(source, fs) => + loop(source, Frame(fs, stack)) + case Pure(v) => + stack match { + case Nil => poll(onOutput(v)) + case Frame(head, tail) => + loop(head(v), tail) + } + case Eval(fa) => + poll(fa).flatMap(a => continue(Resource.pure(a), stack)) + } - // Indirection for calling `loop` needed because `loop` must be @tailrec - def continue[C](current: Resource[F, C], stack: Stack[C]): F[B] = - loop(current, stack) - - // Interpreter that knows how to evaluate a Resource data structure; - // Maintains its own stack for dealing with Bind chains - @tailrec def loop[C](current: Resource[F, C], stack: Stack[C]): F[B] = - current match { - case Allocate(resource) => - F.bracketFull(resource) { - case (a, _) => - stack match { - case Nil => onOutput(a) - case Frame(head, tail) => continue(head(a), tail) - } - } { - case ((_, release), outcome) => - onRelease(release, ExitCase.fromOutcome(outcome)) - } - case Bind(source, fs) => - loop(source, Frame(fs, stack)) - case Pure(v) => - stack match { - case Nil => onOutput(v) - case Frame(head, tail) => - loop(head(v), tail) - } - case Eval(fa) => - fa.flatMap(a => continue(Resource.pure(a), stack)) - } - loop(this, Nil) + loop(this, Nil) + + } } /** @@ -467,23 +470,22 @@ sealed abstract class Resource[F[_], +A] extends Serializable { case object Nil extends Stack[B] final case class Frame[AA, BB](head: AA => Resource[F, BB], tail: Stack[BB]) extends Stack[AA] - - // Indirection for calling `loop` needed because `loop` must be @tailrec - def continue[C]( - current: Resource[F, C], - stack: Stack[C], - release: ExitCase => F[Unit]): F[(B, ExitCase => F[Unit])] = - loop(current, stack, release) - - // Interpreter that knows how to evaluate a Resource data structure; - // Maintains its own stack for dealing with Bind chains - @tailrec def loop[C]( - current: Resource[F, C], - stack: Stack[C], - release: ExitCase => F[Unit]): F[(B, ExitCase => F[Unit])] = - current match { - case Allocate(resource) => - F uncancelable { poll => + F uncancelable { poll => + // Indirection for calling `loop` needed because `loop` must be @tailrec + def continue[C]( + current: Resource[F, C], + stack: Stack[C], + release: ExitCase => F[Unit]): F[(B, ExitCase => F[Unit])] = + loop(current, stack, release) + + // Interpreter that knows how to evaluate a Resource data structure; + // Maintains its own stack for dealing with Bind chains + @tailrec def loop[C]( + current: Resource[F, C], + stack: Stack[C], + release: ExitCase => F[Unit]): F[(B, ExitCase => F[Unit])] = + current match { + case Allocate(resource) => resource(poll) flatMap { case (b, rel) => // Insert F.unit to emulate defer for stack-safety @@ -505,29 +507,29 @@ sealed abstract class Resource[F[_], +A] extends Serializable { F.pure((b, rel2)) case Frame(head, tail) => - poll(continue(head(b), tail, rel2)) + (poll(F.unit) >> continue(head(b), tail, rel2)) .onCancel(rel(ExitCase.Canceled)) .onError { case e => rel(ExitCase.Errored(e)).handleError(_ => ()) } } } - } - case Bind(source, fs) => - loop(source, Frame(fs, stack), release) + case Bind(source, fs) => + loop(source, Frame(fs, stack), release) - case Pure(v) => - stack match { - case Nil => - (v: B, release).pure[F] - case Frame(head, tail) => - loop(head(v), tail, release) - } + case Pure(v) => + stack match { + case Nil => + (v: B, release).pure[F] + case Frame(head, tail) => + loop(head(v), tail, release) + } - case Eval(fa) => - fa.flatMap(a => continue(Resource.pure(a), stack, release)) - } + case Eval(fa) => + poll(fa).flatMap(a => continue(Resource.pure(a), stack, release)) + } - loop(this, Nil, _ => F.unit) + loop(this, Nil, _ => F.unit) + } } /** diff --git a/tests/shared/src/test/scala/cats/effect/ResourceSuite.scala b/tests/shared/src/test/scala/cats/effect/ResourceSuite.scala index 873c1004c0..46f5a2595c 100644 --- a/tests/shared/src/test/scala/cats/effect/ResourceSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/ResourceSuite.scala @@ -154,6 +154,26 @@ class ResourceSuite extends BaseScalaCheckSuite with DisciplineSuite { forAll { (fa: IO[String]) => assertEqv(Resource.eval(fa).use(IO.pure), fa) } } + real("eval - uncancelable timeout is not canceled") { + Resource.eval(IO.uncancelable(_ => IO.sleep(100.millis))).timeout(10.millis).use_ + } + + real("eval - uncancelable continuation") { + val res = Resource + .make(IO.pure(42))(_ => IO.unit) + .flatMap(_ => Resource.eval(IO.uncancelable { _ => IO.canceled })) + + for { + ctr <- IO.ref(0) + fib <- IO.uncancelable { poll => + poll(res.allocatedCase).flatMap { _ => ctr.update(_ + 1) } + }.start + _ <- fib.join + c <- ctr.get + _ <- IO { assertEquals(c, 1) } + } yield () + } + ticked("eval - interruption") { implicit ticker => def resource(d: Deferred[IO, Int]): Resource[IO, Unit] = for { @@ -722,16 +742,6 @@ class ResourceSuite extends BaseScalaCheckSuite with DisciplineSuite { assertEquals(released, acquired) } - tickedProperty("combineK - behave like orElse when underlying effect does") { - implicit ticker => - forAll { (r1: Resource[IO, Int], r2: Resource[IO, Int]) => - val lhs = r1.orElse(r2) - val rhs = r1 <+> r2 - - assertEqv(lhs, rhs) - } - } - tickedProperty("combineK - behave like underlying effect") { implicit ticker => forAll { (ot1: OptionT[IO, Int], ot2: OptionT[IO, Int]) => val lhs = Resource.eval(ot1 <+> ot2).use(OptionT.pure[IO](_)).value