Skip to content

Commit

Permalink
Merge pull request #706 from Avasil/bracket-dont-evaluate-use-on-cancel
Browse files Browse the repository at this point in the history
Bracket: Do not evaluate "use" if "acquire" is canceled
  • Loading branch information
djspiewak authored Dec 22, 2019
2 parents 862baf8 + c554dfe commit 777ae0c
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 8 deletions.
3 changes: 2 additions & 1 deletion .scalafmt.conf
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ continuationIndent.defnSite = 2
assumeStandardLibraryStripMargin = true
danglingParentheses = true
rewrite.rules = [AvoidInfix, SortImports, RedundantBraces, RedundantParens, SortModifiers]
docstrings = JavaDoc
docstrings = JavaDoc
lineEndings=preserve
19 changes: 12 additions & 7 deletions core/shared/src/main/scala/cats/effect/internals/IOBracket.scala
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,21 @@ private[effect] object IOBracket {
def run(): Unit = result match {
case Right(a) =>
val frame = new BracketReleaseFrame[A, B](a, release)
val onNext = {
val fb = try use(a)
catch { case NonFatal(e) => IO.raiseError(e) }
fb.flatMap(frame)
}

// Registering our cancelable token ensures that in case
// cancellation is detected, `release` gets called
deferredRelease.complete(frame.cancel)
// Actual execution
IORunLoop.startCancelable(onNext, conn, cb)

// Check if IO wasn't already cancelled in acquire
if (!conn.isCanceled) {
val onNext = {
val fb = try use(a)
catch { case NonFatal(e) => IO.raiseError(e) }
fb.flatMap(frame)
}
// Actual execution
IORunLoop.startCancelable(onNext, conn, cb)
}

case error @ Left(_) =>
deferredRelease.complete(IO.unit)
Expand Down
20 changes: 20 additions & 0 deletions laws/shared/src/test/scala/cats/effect/IOTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,26 @@ class IOTests extends BaseTestsSuite {
effect shouldBe 1
}

testAsync("bracket does not evaluate use on cancel") { implicit ec =>
implicit val contextShift = ec.contextShift[IO]
implicit val timer = ec.timer[IO]

var use = false
var release = false

val task = IO
.sleep(2.second)
.bracket(_ => IO { use = true })(_ => IO { release = true })
.timeoutTo[Unit](1.second, IO.never)

val f = task.unsafeToFuture()
ec.tick(2.second)

f.value shouldBe None
use shouldBe false
release shouldBe true
}

test("unsafeRunSync works for IO.cancelBoundary") {
val io = IO.cancelBoundary *> IO(1)
io.unsafeRunSync() shouldBe 1
Expand Down

0 comments on commit 777ae0c

Please sign in to comment.