diff --git a/core-tests/shared/src/test/scala/zio/ZIOSpec.scala b/core-tests/shared/src/test/scala/zio/ZIOSpec.scala index 0c123750dac4..1d3e7d5dc6c9 100644 --- a/core-tests/shared/src/test/scala/zio/ZIOSpec.scala +++ b/core-tests/shared/src/test/scala/zio/ZIOSpec.scala @@ -148,7 +148,7 @@ object ZIOSpec extends ZIOBaseSpec { d <- cache } yield assert(a)(equalTo(b)) && assert(b)(not(equalTo(c))) && assert(c)(equalTo(d)) }, - testM("correctly handled an infinite duration time to live") { + testM("correctly handles an infinite duration time to live") { for { ref <- Ref.make(0) getAndIncrement = ref.modify(curr => (curr, curr + 1)) @@ -159,6 +159,28 @@ object ZIOSpec extends ZIOBaseSpec { } yield assert((a, b, c))(equalTo((0, 0, 0))) } ), + suite("cachedWith")( + testM("returns new instances after duration") { + def incrementAndGet(ref: Ref[Int]): UIO[Int] = ref.updateAndGet(_ + 1) + for { + ref <- Ref.make(0) + tuple <- incrementAndGet(ref).cachedInvalidate(60.minutes) + (cached, invalidate) = tuple + a <- cached + _ <- TestClock.adjust(59.minutes) + b <- cached + _ <- invalidate + c <- cached + _ <- TestClock.adjust(1.minute) + d <- cached + _ <- TestClock.adjust(59.minutes) + e <- cached + } yield assert(a)(equalTo(b)) && + assert(b)(not(equalTo(c))) && + assert(c)(equalTo(d)) && + assert(d)(not(equalTo(e))) + } + ), suite("catchAllDefect")( testM("recovers from all defects") { val s = "division by zero" diff --git a/core/shared/src/main/scala/zio/ZIO.scala b/core/shared/src/main/scala/zio/ZIO.scala index 0953df13427a..3d7f1c41ab22 100644 --- a/core/shared/src/main/scala/zio/ZIO.scala +++ b/core/shared/src/main/scala/zio/ZIO.scala @@ -285,7 +285,16 @@ sealed trait ZIO[-R, +E, +A] extends Serializable with ZIOPlatformSpecific[R, E, * Returns an effect that, if evaluated, will return the cached result of * this effect. Cached results will expire after `timeToLive` duration. */ - final def cached(timeToLive: Duration): ZIO[R with Clock, Nothing, IO[E, A]] = { + final def cached(timeToLive: Duration): ZIO[R with Clock, Nothing, IO[E, A]] = + cachedInvalidate(timeToLive).map(_._1) + + /** + * Returns an effect that, if evaluated, will return the cached result of + * this effect. Cached results will expire after `timeToLive` duration. In + * addition, returns an effect that can be used to invalidate the current + * cached value before the `timeToLive` duration expires. + */ + final def cachedInvalidate(timeToLive: Duration): ZIO[R with Clock, Nothing, (IO[E, A], UIO[Unit])] = { def compute(start: Long): ZIO[R with Clock, Nothing, Option[(Long, Promise[E, A])]] = for { @@ -303,10 +312,13 @@ sealed trait ZIO[-R, +E, +A] extends Serializable with ZIOPlatformSpecific[R, E, } } + def invalidate(cache: RefM[Option[(Long, Promise[E, A])]]): UIO[Unit] = + cache.set(None) + for { r <- ZIO.environment[R with Clock] cache <- RefM.make[Option[(Long, Promise[E, A])]](None) - } yield get(cache).provide(r) + } yield (get(cache).provide(r), invalidate(cache)) } /**