-
Notifications
You must be signed in to change notification settings - Fork 520
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
Implement a safer Resource.attempt
which releases acquired resource(s) in case of error
#4128
base: series/3.x
Are you sure you want to change the base?
Conversation
Which releases acquired resource(s) in case of error and `MonadCancelThrow` constrain is required in order to do that. This also take away implicit `ApplicativeError` and deprecated the current attempt method.
@@ -1462,7 +1473,7 @@ abstract private[effect] class ResourceMonadError[F[_], E] | |||
implicit protected def F: MonadError[F, E] | |||
|
|||
override def attempt[A](fa: Resource[F, A]): Resource[F, Either[E, A]] = | |||
fa.attempt | |||
fa.attempt(F) |
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 still used the old attempt
but We can't use the new one because the error type is not fixed to Throwable
.
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.
We should mark this instance as deprecated and create a new instance that requires MonadCancelThrow
.
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.
fixed in cb1a293. But now
cats-effect/kernel/shared/src/main/scala/cats/effect/kernel/Resource.scala
Lines 1300 to 1306 in cb1a293
private[effect] trait ResourceHOInstances4 extends ResourceHOInstances5 { | |
implicit def catsEffectMonadErrorForResource[F[_], E]( | |
implicit F0: MonadError[F, E]): MonadError[Resource[F, *], E] = | |
new ResourceMonadError[F, E] { | |
def F = F0 | |
} | |
} |
ResourceMonadError
.
I'm not sure what the best way to fix this. I feel like We can remove it completely. But maybe it broke client code.
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.
We deprecate this one too :)
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.
We deprecate this one but keep the trait hierarchy?
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.
That's a good question. Maybe we should try to deprioritize this instance by moving it to the bottom of the hierarchy. Alternatively, we could just remove the implicit
modifier.
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.
I kept the hierachy the same but removed implicit modifier and deprecated catsEffectMonadErrorForResource
function.
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.
I thought about it more and actually I think it's okay to leave the implicit
there. We don't need to change any prioritization, because we aren't introducing any new instances, and the instance derived from MonadCancelThrow
(which is required for the fixed attempt
) is already higher priority than this one.
By leaving it implicit
, users will get an actionable deprecation instead of non-compiling code.
|
||
def handleErrorWith[B >: A, E](f: E => Resource[F, B])( | ||
implicit F: ApplicativeError[F, E]): Resource[F, B] = | ||
attempt.flatMap { | ||
attempt(F).flatMap { |
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.
imo, We should add a "safe" variant of handleErrorWith
as well to avoid inconsistent behaviors between this and attempt
.
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. You are right, we definitely should do that.
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.
fixed in 3c2d6af
@@ -1462,7 +1473,7 @@ abstract private[effect] class ResourceMonadError[F[_], E] | |||
implicit protected def F: MonadError[F, E] | |||
|
|||
override def attempt[A](fa: Resource[F, A]): Resource[F, Either[E, A]] = | |||
fa.attempt | |||
fa.attempt(F) |
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.
We should mark this instance as deprecated and create a new instance that requires MonadCancelThrow
.
|
||
def handleErrorWith[B >: A, E](f: E => Resource[F, B])( | ||
implicit F: ApplicativeError[F, E]): Resource[F, B] = | ||
attempt.flatMap { | ||
attempt(F).flatMap { |
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. You are right, we definitely should do that.
def attempt[E](F: ApplicativeError[F, E]): Resource[F, Either[E, A]] = { | ||
implicit val x: ApplicativeError[F, E] = F |
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.
We can do a trick here to automatically fix more callsites without requirng them to recompile: we can pattern match on F
and if it is Sync
or Async
then we can use the fixed implementation instead.
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.
I'm not sure how to implement your suggestion.
I tried:
@deprecated("Use overload with MonadCancelThrow", "3.6.0")
def attempt[E](F: ApplicativeError[F, E]): Resource[F, Either[E, A]] =
F match {
case x: Async[_] =>
implicit val x: MonadCancelThrow[F] = x
attempt
case x: Sync[_] => attempt(x)
implicit val x: MonadCancelThrow[F] = x
attempt
case _ => ???
}
But it doesn't work as attempt
with MonadCancelThrow
returns Resource[F, Either[Throwable, A]]
but not Resource[F, Either[E, 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.
You can cast unsafely. If F
is Sync
then E
must be Throwable
. Also I just realized since Async <: Sync
we only need to match for Sync
.
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.
Fixed it 3b7d0ee
|
||
def attempt(implicit F: MonadCancelThrow[F]): Resource[F, Either[Throwable, A]] = | ||
Resource.applyCase[F, Either[Throwable, A]] { | ||
allocatedCase.attempt.map { |
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.
question: shouldn't the allocation itself be interruptible?
like Resource.applyFull { poll => poll(allocatedCase).attempt.map...
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 looks right. Let's make sure to add a test case for this.
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.
Deprecated handleErrorWith with ApplicativeError
@@ -66,7 +66,7 @@ private[effect] object JvmCpuStarvationMetrics { | |||
case (mbeanServer, _) => IO.blocking(mbeanServer.unregisterMBean(mBeanObjectName)) | |||
} | |||
.map(_._2) | |||
.handleErrorWith[CpuStarvationMetrics, Throwable] { th => | |||
.handleErrorWith[CpuStarvationMetrics] { th => |
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 change because handleErrorWith[E, A]
now requires explicit ApplicativeError
Fix F is `Sync`, We know that it has to be a `MonadCancelThrow`, therefore We can use safer attempt and `unsafely` cast the result.
Also remove implicit modifier from it.
357cd94
to
206020c
Compare
.flatMap { ref => | ||
val resource = Resource.make(ref.update(_ + 1))(_ => ref.update(_ + 1)) | ||
val error = Resource.raiseError[IO, Unit, Throwable](new Exception) | ||
(resource *> error).attempt.use { _ => ref.get.map { _ must be_==(2) } } |
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.
instead of ignoring the value _
we can check that it's a Left
.
Co-authored-by: Arman Bilge <[email protected]>
MonadCancelThrow instance (which is required for the fixed attempt) is already higher priority than this one. By leaving it implicit, users will get an actionable deprecation instead of non-compiling code.
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.
Looking good, one last comment :)
Co-authored-by: Arman Bilge <[email protected]>
337f065
to
6bda660
Compare
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.
nice work :)
This is my solution for #3757,
As I discussed with @armanbilge, the new method requires
MonadCancelThrow
in order to useResource.allocatedCase
which handles releasing acquired resource(s) in case of error. This pr also removed implicitApplicativeError
and deprecated the current attempt method.This is a draft as it still needs to add tests and address some issues that I'll comment in the pr. But please give me early feedback.