-
Notifications
You must be signed in to change notification settings - Fork 535
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Properly propogate Writer and State effects in bracketCase #374
Conversation
Codecov Report
@@ Coverage Diff @@
## master #374 +/- ##
==========================================
+ Coverage 88.56% 89.48% +0.91%
==========================================
Files 69 69
Lines 1802 1845 +43
Branches 197 192 -5
==========================================
+ Hits 1596 1651 +55
+ Misses 206 194 -12 |
The same technique can be applied to the other transformers: EitherT, OptionT and IorT (#365). |
|
release(a, res).value | ||
(release: (A, ExitCase[Throwable]) => WriterT[F, L, Unit]): WriterT[F, L, B] = | ||
WriterT.liftF(Ref.of[F, Option[L]](None)).flatMap { ref => | ||
uncancelable(acquire).flatMap { a => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This construct is not correct: uncancelable(acquire).flatMap
The gist is this: a task that allocates a resource, like acquire
, always has to be in the context of a bracket
. In this case it is not and that flatMap
can be cancelled via the auto-cancelation mechanisms. The problem is that it can't be reproduced with cats.effect.IO
, only because cats.effect.IO
does not auto-fork.
If we cannot fix it somehow, then this instance needs to be scheduled for deletion in version 2.0 because it is broken.
@oleg-py can you also take a look when you've got some time?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, worse case scenario we add a @deprecated
tag to it warning people that it is broken.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, this is certainly broken.
I think we could make release
idempotent here and use bracket
second time instead of uncancelable
, if we want to keep this instance.
As a manifestation of what I was saying, the problem with I had to deactivate |
Shall we merge #376 first and then fix the instance with this PR? |
Yes, that order sounds good as now we can really see the problem. |
F.bracketCase(F.pure(a))(use.andThen(_.run)){ (a, res) => | ||
release(a, res).value | ||
(release: (A, ExitCase[Throwable]) => WriterT[F, L, Unit]): WriterT[F, L, B] = | ||
WriterT.liftF(Ref.of[F, Option[L]](None)).flatMap { ref => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we have a Monoid, can't we just use Ref.of[F, L](L.empty)
and save on Option
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I don't think this will work, given the fact that we couldn't check if an L
is empty, like we can with Option
(atleast without requiring an extra Eq[L]
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean that getOrElse
in the end?
I see. Didn't notice that, thanks for clearing this up for me 👍
I have merged #376 in master and would like to do a release soon. Any progress on this? |
Have we come to a consensus on the transformer effect propagation in I'm working on #365 and it's not clear for me what strategy for Can we clarify sematics? |
@catostrophe |
e74153c
to
30c668f
Compare
Added OptionT and EitherT: scala> OptionT(IO(Option(1))) ^
res0: cats.data.OptionT[cats.effect.IO,Int] = OptionT(IO$1900219430)
scala> Sync[OptionT[IO, ?]].bracket(res0)(_ => OptionT(IO(Option(2))))(_ => OptionT.none[IO, Unit])
res1: cats.data.OptionT[cats.effect.IO,Int] = OptionT(IO$1768249421)
scala> res1.value.unsafeRunSync
res2: Option[Int] = None scala> EitherT(IO(Either.right[String, Int](1)))
res0: cats.data.EitherT[cats.effect.IO,String,Int] = EitherT(IO$983210249)
scala> Sync[EitherT[IO, String, ?]].bracket(res10)(_ => EitherT(IO(Either.right[String, Int](2))))(_ => EitherT(IO(Either.left[String, Unit]("Hi"))))
res1: cats.data.EitherT[cats.effect.IO,String,Int] = EitherT(IO$111852734)
scala> res1.value.unsafeRunSync
res2: Either[String,Int] = Left(Hi) |
F.unit // nothing to release | ||
} | ||
}) | ||
EitherT.liftF(Ref.of[F, Option[L]](None)).flatMap { ref => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
flatMapF
F.unit | ||
}) | ||
//Boolean represents if release returned None | ||
OptionT.liftF(Ref.of[F, Boolean](false)).flatMap { ref => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And here
} { case ((s, a), br) => | ||
release(a, br).run(s).void | ||
(release: (A, ExitCase[Throwable]) => StateT[F, S, Unit]): StateT[F, S, B] = | ||
StateT.liftF(Ref.of[F, Option[S]](None)).flatMap { ref => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And here
(release: (A, ExitCase[Throwable]) => WriterT[F, L, Unit]): WriterT[F, L, B] = | ||
WriterT.liftF(Ref.of[F, Option[L]](None)).flatMap { ref => | ||
acquire.flatMap { a => | ||
WriterT( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And here
Guys, no progress on these instances? I my PR I introduced this: @deprecated("WARNING: currently the Async[WriterT[F, L, ?]] instance is broken!", "1.1.0") For a Can we fix them by any means? Can you make progress on it? |
This issues seems to be fixed. We only need to finish the review. |
I'm not sure its fixed unfortunately, the issue with propogation in |
This should fix the failing tests: import cats.instances.tuple._
def bracketCase[A, B](acquire: WriterT[F, L, A])
(use: A => WriterT[F, L, B])
(release: (A, ExitCase[Throwable]) => WriterT[F, L, Unit]): WriterT[F, L, B] =
WriterT(
Ref[F].of(none[L]).flatMap { ref =>
F.bracketCase(acquire.run) { la =>
WriterT(la.pure[F]).flatMap(use).run
} { case ((_, a), ec) =>
val r = release(a, ec).run
if (ec == ExitCase.Completed)
r flatMap { case (l, _) => ref.set(l.some) }
else
r.void
}.flatMap { lb =>
ref.get.map(_.fold(lb)(l => lb.leftMap(_ |+| l)))
}
}
) |
Oh, and Option[L] can be replaced with L here, as @vpavkin noticed. I don't compare it with empty. Something like: import cats.instances.tuple._
def bracketCase[A, B](acquire: WriterT[F, L, A])
(use: A => WriterT[F, L, B])
(release: (A, ExitCase[Throwable]) => WriterT[F, L, Unit]): WriterT[F, L, B] =
WriterT(
Ref[F].of(L.empty).flatMap { ref =>
F.bracketCase(acquire.run) { la =>
WriterT(la.pure[F]).flatMap(use).run
} { case ((_, a), ec) =>
val r = release(a, ec).run
if (ec == ExitCase.Completed)
r flatMap { case (l, _) => ref.set(l) }
else
r.void
}.flatMap { lb =>
ref.get.map(l => lb.leftMap(_ |+| l))
}
}
) |
CI seems to be happy and the |
🎉 |
Should fix #371.
With this change:
Without this change: