Open
Description
The current implementation of bracketState
does not guarantee that:
use
will be cancelled when fiber is cancelledcommit
will be executed iffa
succeeds
The following code demonstrates the problem:
import cats.effect._
import java.time.Instant
import scala.concurrent.duration._
import tofu.syntax.bracket._
object Demo extends IOApp {
def log(text: String): IO[Unit] = IO(println(s"${Instant.now}: $text"))
val slow: IO[Unit] = for {
_ <- log("start")
_ <- Timer[IO].sleep(3.seconds)
_ <- log("finish")
} yield ()
override def run(args: List[String]): IO[ExitCode] =
slow.bracketState { _ =>
log("use").as((), ())
} { _ =>
log("commit")
}.timeoutTo(1.second, log("timeout"))
.as(ExitCode.Success)
}
The output looks like this:
2021-12-09T17:18:30.869187693Z: start
2021-12-09T17:18:33.897543168Z: finish
2021-12-09T17:18:33.899531757Z: use
2021-12-09T17:18:33.900238459Z: timeout
The "finish" message is still printed although the fiber is cancelled by timeoutTo
. This is because the first FG.bracket
call in bracketState
code makes its init
block uncancellable. The "commit" message is not printed because bracketCase
calls it in the use
block of the first FG.bracket
, but it is not guaranteed that use
will be executed after successful init
.
This behavior breaks bracketModify
and MVar
-based tofu.memo.Cached
.
Metadata
Metadata
Assignees
Labels
No labels