Skip to content

Commit

Permalink
Merge pull request #124 from moka-rs/ch123-get-with-if
Browse files Browse the repository at this point in the history
Add `get_with_if` method to `sync` and `future` caches
  • Loading branch information
tatsuya6502 committed May 19, 2022
2 parents b9c03c4 + 7ddf920 commit 5a18eb4
Show file tree
Hide file tree
Showing 4 changed files with 601 additions and 133 deletions.
14 changes: 12 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Moka — Change Log

## Version 0.8.4

### Added

- Add `get_with_if` method to the following caches: ([#123][gh-issue-0123])
- `sync::Cache`
- `sync::SegmentedCache`
- `future::Cache`


## Version 0.8.3

### Changed
Expand Down Expand Up @@ -312,10 +322,10 @@ The minimum supported Rust version (MSRV) is now 1.51.0 (2021-03-25).
[caffeine-git]: https://github.com/ben-manes/caffeine
[quanta-crate]: https://crates.io/crates/quanta

[panic_in_quanta]: https://github.com/moka-rs/moka#integer-overflow-in-quanta-crate-on-some-x8664-machines

[panic_in_quanta]: https://github.com/moka-rs/moka#integer-overflow-in-quanta-crate-on-some-x86_64-machines
[resolving-error-on-32bit]: https://github.com/moka-rs/moka#compile-errors-on-some-32-bit-platforms

[gh-issue-0123]: https://github.com/moka-rs/moka/issues/123/
[gh-issue-0119]: https://github.com/moka-rs/moka/issues/119/
[gh-issue-0107]: https://github.com/moka-rs/moka/issues/107/
[gh-issue-0072]: https://github.com/moka-rs/moka/issues/72/
Expand Down
219 changes: 185 additions & 34 deletions src/future/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ where
self.try_get_with(key, init).await
}

/// Ensures the value of the key exists by inserting the output of the init
/// Ensures the value of the key exists by inserting the output of the `init`
/// future if not exist, and returns a _clone_ of the value.
///
/// This method prevents to resolve the init future multiple times on the same
Expand Down Expand Up @@ -497,7 +497,29 @@ where
pub async fn get_with(&self, key: K, init: impl Future<Output = V>) -> V {
let hash = self.base.hash(&key);
let key = Arc::new(key);
self.get_or_insert_with_hash_and_fun(key, hash, init).await
let replace_if = None as Option<fn(&V) -> bool>;
self.get_or_insert_with_hash_and_fun(key, hash, init, replace_if)
.await
}

/// Works like [`get_with`](#method.get_with), but takes an additional
/// `replace_if` closure.
///
/// This method will resolve the `init` feature and insert the output to the
/// cache when:
///
/// - The key does not exist.
/// - Or, `replace_if` closure returns `true`.
pub async fn get_with_if(
&self,
key: K,
init: impl Future<Output = V>,
replace_if: impl FnMut(&V) -> bool,
) -> V {
let hash = self.base.hash(&key);
let key = Arc::new(key);
self.get_or_insert_with_hash_and_fun(key, hash, init, Some(replace_if))
.await
}

/// Try to ensure the value of the key exists by inserting an `Ok` output of the
Expand Down Expand Up @@ -830,9 +852,16 @@ where
key: Arc<K>,
hash: u64,
init: impl Future<Output = V>,
mut replace_if: Option<impl FnMut(&V) -> bool>,
) -> V {
if let Some(v) = self.base.get_with_hash(&key, hash) {
return v;
match (self.base.get_with_hash(&key, hash), &mut replace_if) {
(Some(v), None) => return v,
(Some(v), Some(cond)) => {
if !cond(&v) {
return v;
};
}
_ => (),
}

match self
Expand Down Expand Up @@ -1602,9 +1631,9 @@ mod tests {

// This test will run five async tasks:
//
// Task1 will be the first task to call `get_with` for a key, so
// its async block will be evaluated and then a &str value "task1" will be
// inserted to the cache.
// Task1 will be the first task to call `get_with` for a key, so its async
// block will be evaluated and then a &str value "task1" will be inserted to
// the cache.
let task1 = {
let cache1 = cache.clone();
async move {
Expand All @@ -1620,9 +1649,9 @@ mod tests {
}
};

// Task2 will be the second task to call `get_with` for the same
// key, so its async block will not be evaluated. Once task1's async block
// finishes, it will get the value inserted by task1's async block.
// Task2 will be the second task to call `get_with` for the same key, so its
// async block will not be evaluated. Once task1's async block finishes, it
// will get the value inserted by task1's async block.
let task2 = {
let cache2 = cache.clone();
async move {
Expand All @@ -1633,11 +1662,11 @@ mod tests {
}
};

// Task3 will be the third task to call `get_with` for the same
// key. By the time it calls, task1's async block should have finished
// already and the value should be already inserted to the cache. So its
// async block will not be evaluated and will get the value insert by task1's
// async block immediately.
// Task3 will be the third task to call `get_with` for the same key. By the
// time it calls, task1's async block should have finished already and the
// value should be already inserted to the cache. So its async block will not
// be evaluated and will get the value inserted by task1's async block
// immediately.
let task3 = {
let cache3 = cache.clone();
async move {
Expand Down Expand Up @@ -1675,6 +1704,129 @@ mod tests {
futures_util::join!(task1, task2, task3, task4, task5);
}

#[tokio::test]
async fn get_with_if() {
let cache = Cache::new(100);
const KEY: u32 = 0;

// This test will run seven async tasks:
//
// Task1 will be the first task to call `get_with_if` for a key, so its async
// block will be evaluated and then a &str value "task1" will be inserted to
// the cache.
let task1 = {
let cache1 = cache.clone();
async move {
// Call `get_with_if` immediately.
let v = cache1
.get_with_if(
KEY,
async {
// Wait for 300 ms and return a &str value.
Timer::after(Duration::from_millis(300)).await;
"task1"
},
|_v| unreachable!(),
)
.await;
assert_eq!(v, "task1");
}
};

// Task2 will be the second task to call `get_with_if` for the same key, so
// its async block will not be evaluated. Once task1's async block finishes,
// it will get the value inserted by task1's async block.
let task2 = {
let cache2 = cache.clone();
async move {
// Wait for 100 ms before calling `get_with_if`.
Timer::after(Duration::from_millis(100)).await;
let v = cache2
.get_with_if(KEY, async { unreachable!() }, |_v| unreachable!())
.await;
assert_eq!(v, "task1");
}
};

// Task3 will be the third task to call `get_with_if` for the same key. By
// the time it calls, task1's async block should have finished already and
// the value should be already inserted to the cache. Also task3's
// `replace_if` closure returns `false`. So its async block will not be
// evaluated and will get the value inserted by task1's async block
// immediately.
let task3 = {
let cache3 = cache.clone();
async move {
// Wait for 350 ms before calling `get_with_if`.
Timer::after(Duration::from_millis(350)).await;
let v = cache3
.get_with_if(KEY, async { unreachable!() }, |v| {
assert_eq!(v, &"task1");
false
})
.await;
assert_eq!(v, "task1");
}
};

// Task4 will be the fourth task to call `get_with_if` for the same key. The
// value should have been already inserted to the cache by task1. However
// task4's `replace_if` closure returns `true`. So its async block will be
// evaluated to replace the current value.
let task4 = {
let cache4 = cache.clone();
async move {
// Wait for 400 ms before calling `get_with_if`.
Timer::after(Duration::from_millis(400)).await;
let v = cache4
.get_with_if(KEY, async { "task4" }, |v| {
assert_eq!(v, &"task1");
true
})
.await;
assert_eq!(v, "task4");
}
};

// Task5 will call `get` for the same key. It will call when task1's async
// block is still running, so it will get none for the key.
let task5 = {
let cache5 = cache.clone();
async move {
// Wait for 200 ms before calling `get`.
Timer::after(Duration::from_millis(200)).await;
let maybe_v = cache5.get(&KEY);
assert!(maybe_v.is_none());
}
};

// Task6 will call `get` for the same key. It will call after task1's async
// block finished, so it will get the value insert by task1's async block.
let task6 = {
let cache6 = cache.clone();
async move {
// Wait for 350 ms before calling `get`.
Timer::after(Duration::from_millis(350)).await;
let maybe_v = cache6.get(&KEY);
assert_eq!(maybe_v, Some("task1"));
}
};

// Task7 will call `get` for the same key. It will call after task4's async
// block finished, so it will get the value insert by task4's async block.
let task7 = {
let cache7 = cache.clone();
async move {
// Wait for 450 ms before calling `get`.
Timer::after(Duration::from_millis(450)).await;
let maybe_v = cache7.get(&KEY);
assert_eq!(maybe_v, Some("task4"));
}
};

futures_util::join!(task1, task2, task3, task4, task5, task6, task7);
}

#[tokio::test]
async fn try_get_with() {
use std::sync::Arc;
Expand All @@ -1691,9 +1843,9 @@ mod tests {

// This test will run eight async tasks:
//
// Task1 will be the first task to call `get_with` for a key, so
// its async block will be evaluated and then an error will be returned.
// Nothing will be inserted to the cache.
// Task1 will be the first task to call `get_with` for a key, so its async
// block will be evaluated and then an error will be returned. Nothing will
// be inserted to the cache.
let task1 = {
let cache1 = cache.clone();
async move {
Expand All @@ -1709,10 +1861,9 @@ mod tests {
}
};

// Task2 will be the second task to call `get_with` for the same
// key, so its async block will not be evaluated. Once task1's async block
// finishes, it will get the same error value returned by task1's async
// block.
// Task2 will be the second task to call `get_with` for the same key, so its
// async block will not be evaluated. Once task1's async block finishes, it
// will get the same error value returned by task1's async block.
let task2 = {
let cache2 = cache.clone();
async move {
Expand All @@ -1723,11 +1874,11 @@ mod tests {
}
};

// Task3 will be the third task to call `get_with` for the same
// key. By the time it calls, task1's async block should have finished
// already, but the key still does not exist in the cache. So its async block
// will be evaluated and then an okay &str value will be returned. That value
// will be inserted to the cache.
// Task3 will be the third task to call `get_with` for the same key. By the
// time it calls, task1's async block should have finished already, but the
// key still does not exist in the cache. So its async block will be
// evaluated and then an okay &str value will be returned. That value will be
// inserted to the cache.
let task3 = {
let cache3 = cache.clone();
async move {
Expand All @@ -1744,9 +1895,9 @@ mod tests {
}
};

// Task4 will be the fourth task to call `get_with` for the same
// key. So its async block will not be evaluated. Once task3's async block
// finishes, it will get the same okay &str value.
// Task4 will be the fourth task to call `get_with` for the same key. So its
// async block will not be evaluated. Once task3's async block finishes, it
// will get the same okay &str value.
let task4 = {
let cache4 = cache.clone();
async move {
Expand All @@ -1757,10 +1908,10 @@ mod tests {
}
};

// Task5 will be the fifth task to call `get_with` for the same
// key. So its async block will not be evaluated. By the time it calls,
// task3's async block should have finished already, so its async block will
// not be evaluated and will get the value insert by task3's async block
// Task5 will be the fifth task to call `get_with` for the same key. So its
// async block will not be evaluated. By the time it calls, task3's async
// block should have finished already, so its async block will not be
// evaluated and will get the value insert by task3's async block
// immediately.
let task5 = {
let cache5 = cache.clone();
Expand Down

0 comments on commit 5a18eb4

Please sign in to comment.