Skip to content

Commit

Permalink
Add a parameter storeOnlyIfValue in Cache.make.*
Browse files Browse the repository at this point in the history
  • Loading branch information
kajebiii committed Sep 26, 2023
1 parent a0545cb commit 1184a17
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 13 deletions.
61 changes: 50 additions & 11 deletions zio-cache/shared/src/main/scala/zio/cache/Cache.scala
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,10 @@ object Cache {
def make[Key, Environment, Error, Value](
capacity: Int,
timeToLive: Duration,
lookup: Lookup[Key, Environment, Error, Value]
lookup: Lookup[Key, Environment, Error, Value],
storeOnlyIfValue: Boolean = false
)(implicit trace: Trace): URIO[Environment, Cache[Key, Error, Value]] =
makeWith(capacity, lookup)(_ => timeToLive)
makeWith(capacity, lookup, storeOnlyIfValue)(_ => timeToLive)

/**
* Constructs a new cache with the specified capacity, time to live, and
Expand All @@ -114,11 +115,12 @@ object Cache {
*/
def makeWith[Key, Environment, Error, Value](
capacity: Int,
lookup: Lookup[Key, Environment, Error, Value]
lookup: Lookup[Key, Environment, Error, Value],
storeOnlyIfValue: Boolean = false
)(
timeToLive: Exit[Error, Value] => Duration
)(implicit trace: Trace): URIO[Environment, Cache[Key, Error, Value]] =
makeWithKey(capacity, lookup)(timeToLive, identity)
makeWithKey(capacity, lookup, storeOnlyIfValue)(timeToLive, identity)

/**
* Constructs a new cache with the specified capacity, time to live, and
Expand All @@ -132,7 +134,8 @@ object Cache {
*/
def makeWithKey[In, Key, Environment, Error, Value](
capacity: Int,
lookup: Lookup[In, Environment, Error, Value]
lookup: Lookup[In, Environment, Error, Value],
storeOnlyIfValue: Boolean = false
)(
timeToLive: Exit[Error, Value] => Duration,
keyBy: In => Key
Expand Down Expand Up @@ -230,7 +233,12 @@ object Cache {
map.remove(k, value)
get(in)
} else {
ZIO.done(exit)
exit match {
case Left(exit) =>
ZIO.done(exit)
case Right(value) =>
ZIO.done(Exit.Success(value))
}
}
case MapValue.Refreshing(
promiseInProgress,
Expand All @@ -241,7 +249,12 @@ object Cache {
if (hasExpired(ttl)) {
promiseInProgress.await
} else {
ZIO.done(currentResult)
currentResult match {
case Left(exit) =>
ZIO.done(exit)
case Right(value) =>
ZIO.done(Exit.Success(value))
}
}
}
}
Expand All @@ -266,8 +279,9 @@ object Cache {
map.remove(k, value)
get(in)
} else {
val rollbackResultIfError = if (storeOnlyIfValue) Some(completedResult) else None
// Only trigger the lookup if we're still the current value, `completedResult`
lookupValueOf(in, promise).when {
lookupValueOf(in, promise, rollbackResultIfError).when {
map.replace(k, completedResult, MapValue.Refreshing(promise, completedResult))
}
}
Expand All @@ -292,7 +306,11 @@ object Cache {
def size(implicit trace: Trace): UIO[Int] =
ZIO.succeed(map.size)

private def lookupValueOf(in: In, promise: Promise[Error, Value]): IO[Error, Value] =
private def lookupValueOf(
in: In,
promise: Promise[Error, Value],
rollbackResultIfError: Option[MapValue.Complete[Key, Error, Value]] = None
): IO[Error, Value] =
ZIO.suspendSucceed {
val key = keyBy(in)
lookup(in)
Expand All @@ -302,7 +320,28 @@ object Cache {
val now = Unsafe.unsafe(implicit u => clock.unsafe.instant())
val entryStats = EntryStats(now)

map.put(key, MapValue.Complete(new MapKey(key), exit, entryStats, now.plus(timeToLive(exit))))
if (!storeOnlyIfValue)
map.put(
key,
MapValue.Complete(new MapKey(key), Left(exit), entryStats, now.plus(timeToLive(exit)))
)
else {
exit match {
case Exit.Success(value) =>
map.put(
key,
MapValue.Complete(new MapKey(key), Right(value), entryStats, now.plus(timeToLive(exit)))
)
case Exit.Failure(cause) =>
rollbackResultIfError match {
case None =>
map.remove(key)
case Some(rollbackValue) =>
map.put(key, rollbackValue)
}
}
}

promise.done(exit) *> ZIO.done(exit)
}
.onInterrupt(promise.interrupt *> ZIO.succeed(map.remove(key)))
Expand Down Expand Up @@ -334,7 +373,7 @@ object Cache {

final case class Complete[Key, Error, Value](
key: MapKey[Key],
exit: Exit[Error, Value],
exit: Either[Exit[Error, Value], Value],
entryStats: EntryStats,
timeToLive: Instant
) extends MapValue[Key, Error, Value]
Expand Down
93 changes: 91 additions & 2 deletions zio-cache/shared/src/test/scala/zio/cache/CacheSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,17 @@ object CacheSpec extends ZIOSpecDefault {
_ <- cache.refresh(key)
val1 <- cache.get(key).either
_ <- cache.refresh(key)
val2 <- cache.get(key).either
failure2 <- cache.refresh(key).either
failure3 <- cache.get(key).either
_ <- cache.refresh(key)
val2 <- cache.get(key).either
val3 <- cache.get(key).either
} yield assert(failure1)(isLeft(equalTo(error))) &&
assert(failure2)(isLeft(equalTo(error))) &&
assert(failure3)(isLeft(equalTo(error))) &&
assert(val1)(isRight(equalTo(4))) &&
assert(val2)(isRight(equalTo(7)))
assert(val2)(isRight(equalTo(5))) &&
assert(val3)(isRight(equalTo(7)))
},
test("should get the value if the key doesn't exist in the cache") {
check(Gen.int) { salt =>
Expand All @@ -134,6 +138,91 @@ object CacheSpec extends ZIOSpecDefault {
}
}
),
suite("`refresh` method when storeOnlyIfValue is true")(
test("should update the cache with a new value") {
def inc(n: Int) = n * 10

def retrieve(multiplier: Ref[Int])(key: Int) =
multiplier
.updateAndGet(inc)
.map(key * _)

val seed = 1
val key = 123
for {
ref <- Ref.make(seed)
cache <- Cache.make(1, Duration.Infinity, Lookup(retrieve(ref)), storeOnlyIfValue = true)
val1 <- cache.get(key)
_ <- cache.refresh(key)
_ <- cache.get(key)
val2 <- cache.get(key)
} yield assertTrue(val1 == inc(key)) && assertTrue(val2 == inc(val1))
},
test("should update the cache only if lookup return a value, not an error.") {

val error = new RuntimeException("Must be a multiple of 3")

def inc(n: Int) = n + 1

def retrieve(number: Ref[Int])(key: Int) =
number
.updateAndGet(inc)
.flatMap {
case n if n % 3 == 0 =>
ZIO.fail(error)
case n =>
ZIO.succeed(key * n)
}

val seed = 2
val key = 1
for {
ref <- Ref.make(seed)
cache <- Cache.make(1, Duration.Infinity, Lookup(retrieve(ref)), storeOnlyIfValue = true)
failure1 <- cache.get(key).either
_ <- cache.refresh(key)
val1 <- cache.get(key).either
_ <- cache.refresh(key)
val2 <- cache.get(key).either
failure2 <- cache.refresh(key).either
val3 <- cache.get(key).either
_ <- cache.refresh(key)
val4 <- cache.get(key).either
} yield assert(failure1)(isLeft(equalTo(error))) &&
assert(failure2)(isLeft(equalTo(error))) &&
assert(val1)(isRight(equalTo(4))) &&
assert(val2)(isRight(equalTo(5))) &&
assert(val3)(isRight(equalTo(5))) &&
assert(val4)(isRight(equalTo(7)))
},
test("should update only if it is a value when the key doesn't exist in the cache") {

val error = new RuntimeException("Must be a multiple of 3")

def inc(n: Int) = n + 1

def retrieve(number: Ref[Int])(key: Int) =
number
.updateAndGet(inc)
.flatMap {
case n if n % 3 == 0 =>
ZIO.fail(error)
case n =>
ZIO.succeed(key * n)
}

val seed = 2
val key = 1
val cap = 30
for {
ref <- Ref.make(seed)
cache <- Cache.make(cap, Duration.Infinity, Lookup(retrieve(ref)), storeOnlyIfValue = true)
count0 <- cache.size
_ <- ZIO.foreachDiscard(1 to cap)(key => cache.refresh(key).either)
count1 <- cache.size
} yield assertTrue(count0 == 0) && assertTrue(count1 == cap / 3 * 2)
}
),
test("size") {
check(Gen.int) { salt =>
for {
Expand Down

0 comments on commit 1184a17

Please sign in to comment.