Skip to content
Closed
Changes from all 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
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
Resource.makeCaseFull[F, F[B]](poll => poll(F.memoize(fa2).map(_.widen))) { (_, exit) =>
Resource.makeCaseFull[F, F[B]](poll => poll(F.memoize(fa2)).map(_.widen)) { (_, exit) =>

Otherwise I think it can get cancelled right after F.memoize(fa2), and I think that would be not good.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Oh I see, good catch. Probably we should add a test case.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I will look into adding a test case for this, let's see what I can come up with.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I tried to write a test for this but failed to find a reliable way to ensure acquisition is canceled right after the call to F.memoize(fa2) and before the end of .map(_,widen). Is there some tool in the testkit that would allow me to do so and that I am not aware of?

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