Skip to content
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

Add a memoizedAcquire method to Resource #4105

Open
wants to merge 7 commits into
base: series/3.x
Choose a base branch
from
Open
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 20 additions & 12 deletions kernel/shared/src/main/scala/cats/effect/kernel/Resource.scala
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,24 @@ sealed abstract class Resource[F[_], +A] extends Serializable {
K.combineK(allocate(this), allocate(that))
}

/**
* A Resource where the acquire step is done lazily and memoized. This means that acquire
* happens only if and when the `F[A]` value is executed, instead of happening immediately
* upon `use()`. If the `F[A]` value is executed multiple times, acquire happens once only and
* the acquired resource is shared to all callers. The resource is released as normal at the
* end of `use` (whether normal termination, error, or cancelled), if it was acquired.
*/
def memoizedAcquire[B >: A](implicit F: Concurrent[F]): Resource[F, F[B]] = {
Resource.eval(F.ref(List.empty[Resource.ExitCase => F[Unit]])).flatMap { release =>
val fa2 = F.uncancelable { poll =>
poll(allocatedCase).flatMap { case (a, r) => release.update(r :: _).as(a) }
}
Resource.makeCaseFull[F, F[B]](poll => poll(F.memoize(fa2)).map(_.widen)) { (_, exit) =>
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@durban @armanbilge I applied the change here, to ensure acquisition will not be cancelled once F.memoize(fa2) has been successfully run. I tried to come up with a test to ensure the behavior but could not find any reliable way to write something that tries to cancel it at the right time.

Do you have an idea on how I could write such a test?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have an idea on how I could write such a test?

#3425 is an example of a similar PR. Notice the special use of the test runtime.

TestControl.executeEmbed(go, IORuntimeConfig(1, 2))

Further reading:

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I will have a look at this.

release.get.flatMap(_.foldMapM(_(exit)))
}
}
}

}

object Resource extends ResourceFOInstances0 with ResourceHOInstances0 with ResourcePlatform {
Expand Down Expand Up @@ -1377,18 +1395,8 @@ abstract private[effect] class ResourceConcurrent[F[_]]
override def race[A, B](fa: Resource[F, A], fb: Resource[F, B]): Resource[F, Either[A, B]] =
fa.race(fb)

override def memoize[A](fa: Resource[F, A]): Resource[F, Resource[F, A]] = {
Resource.eval(F.ref(List.empty[Resource.ExitCase => F[Unit]])).flatMap { release =>
val fa2 = F.uncancelable { poll =>
poll(fa.allocatedCase).flatMap { case (a, r) => release.update(r :: _).as(a) }
}
Resource
.makeCaseFull[F, F[A]](poll => poll(F.memoize(fa2))) { (_, exit) =>
release.get.flatMap(_.foldMapM(_(exit)))
}
.map(memo => Resource.eval(memo))
}
}
override def memoize[A](fa: Resource[F, A]): Resource[F, Resource[F, A]] =
fa.memoizedAcquire.map(Resource.eval(_))
}

private[effect] trait ResourceClock[F[_]] extends Clock[Resource[F, *]] {
Expand Down
Loading