diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a07cdef..bf741652 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,27 +2,44 @@ ## Version 0.8.0 -### Added +As a part of stabilizing the cache API, the following cache methods have been renamed: -#### Experimental Additions +- `get_or_insert_with(K, F)` → `get_with(K, F)` +- `get_or_try_insert_with(K, F)` → `try_get_with(K, F)` -Please note that the following additions are highly experimental so their APIs will -be frequently changed in next few releases. +Old methods are still available but marked as deprecated. They will be removed in a +future version. -- Add a synchronous cache `moka::dash::Cache`, which uses `dashmap::DashMap` as the - internal storage. ([#99][gh-pull-0099]) -- Add iterator to `moka::dash::Cache`. ([#101][gh-pull-0101]) +Also `policy` method was added to all caches and `blocking` method was added to +`future::Cache`. They return a `Policy` struct or `BlockingOp` struct +respectively. Some uncommon cache methods were moved to these structs, and old +methods were removed without deprecating. + +Please see [#105][gh-pull-0105] for the complete list of the renamed and moved methods. ### Changed +- API stabilization. (Smaller core cache API, shorter names for common methods) + ([#105][gh-pull-0105]) - Performance related: - - Improve performance on `get_or_insert_with`. ([#88][gh-pull-0088]) - - Avoid to calculate the same hash twice in `get`, `insert`, `invalidate`, - etc. ([#90][gh-pull-0090]) + - Improve performance of `get_with` and `try_get_with`. ([#88][gh-pull-0088]) + - Avoid to calculate the same hash twice in `get`, `get_with`, `insert`, + `invalidate`, etc. ([#90][gh-pull-0090]) - Update the minimum versions of dependencies: - - crossbeam-channel from v0.5.2 to v0.5.4. ([#100][gh-pull-0100]) - - scheduled-thread-pool to v0.2.5. ([#103][gh-pull-0103]) - - skeptic to v0.13.5. ([#104][gh-pull-0104]) + - crossbeam-channel to v0.5.4. ([#100][gh-pull-0100]) + - scheduled-thread-pool to v0.2.5. ([#103][gh-pull-0103]) + - (dev-dependency) skeptic to v0.13.5. ([#104][gh-pull-0104]) + +### Added + +#### Experimental Additions + +- Add a synchronous cache `moka::dash::Cache`, which uses `dashmap::DashMap` as the + internal storage. ([#99][gh-pull-0099]) +- Add iterator to `moka::dash::Cache`. ([#101][gh-pull-0101]) + +Please note that the above additions are highly experimental and their APIs will +be frequently changed in next few releases. ## Version 0.7.2 @@ -261,6 +278,7 @@ The minimum supported Rust version (MSRV) is now 1.51.0 (2021-03-25). [gh-issue-0038]: https://github.com/moka-rs/moka/issues/38/ [gh-issue-0031]: https://github.com/moka-rs/moka/issues/31/ +[gh-pull-0105]: https://github.com/moka-rs/moka/pull/105/ [gh-pull-0104]: https://github.com/moka-rs/moka/pull/104/ [gh-pull-0103]: https://github.com/moka-rs/moka/pull/103/ [gh-pull-0101]: https://github.com/moka-rs/moka/pull/101/ diff --git a/README.md b/README.md index 5aac3506..3c880512 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ moka = { version = "0.8", features = ["future"] } The thread-safe, synchronous caches are defined in the `sync` module. -Cache entries are manually added using `insert` or `get_or_insert_with` method, and +Cache entries are manually added using `insert` or `get_with` method, and are stored in the cache until either evicted or manually invalidated. Here's an example of reading and updating a cache by using multiple threads: @@ -162,9 +162,9 @@ fn main() { If you want to atomically initialize and insert a value when the key is not present, you might want to check [the document][doc-sync-cache] for other insertion methods -`get_or_insert_with` and `get_or_try_insert_with`. +`get_with` and `try_get_with`. -[doc-sync-cache]: https://docs.rs/moka/*/moka/sync/struct.Cache.html#method.get_or_insert_with +[doc-sync-cache]: https://docs.rs/moka/*/moka/sync/struct.Cache.html#method.get_with ## Example: Asynchronous Cache @@ -255,9 +255,9 @@ async fn main() { If you want to atomically initialize and insert a value when the key is not present, you might want to check [the document][doc-future-cache] for other insertion methods -`get_or_insert_with` and `get_or_try_insert_with`. +`get_with` and `try_get_with`. -[doc-future-cache]: https://docs.rs/moka/*/moka/future/struct.Cache.html#method.get_or_insert_with +[doc-future-cache]: https://docs.rs/moka/*/moka/future/struct.Cache.html#method.get_with ## Avoiding to clone the value at `get` @@ -468,14 +468,13 @@ $ RUSTFLAGS='--cfg skeptic --cfg trybuild' cargo test \ - [x] `async` optimized caches. (`v0.2.0`) - [x] Size-aware eviction. (`v0.7.0` via [#24](https://github.com/moka-rs/moka/pull/24)) -- [ ] API stabilization. (Smaller core cache API, shorter names for frequently - used methods) +- [X] API stabilization. (Smaller core cache API, shorter names for frequently + used methods) (`v0.8.0` via [#105](https://github.com/moka-rs/moka/pull/105)) - e.g. - `get_or_insert_with(K, F)` → `get_with(K, F)` - `get_or_try_insert_with(K, F)` → `try_get_with(K, F)` - - `get(&Q)` → `get_if_present(&Q)` - `blocking_insert(K, V)` → `blocking().insert(K, V)` - - `time_to_live()` → `config().time_to_live()` + - `time_to_live()` → `policy().time_to_live()` - [ ] Cache statistics. (Hit rate, etc.) - [ ] Notifications on eviction, etc. - [ ] Upgrade TinyLFU to Window-TinyLFU. ([details][tiny-lfu]) diff --git a/src/dash/base_cache.rs b/src/dash/base_cache.rs index 63e4a28d..232e07e6 100644 --- a/src/dash/base_cache.rs +++ b/src/dash/base_cache.rs @@ -14,6 +14,7 @@ use crate::{ housekeeper::{Housekeeper, InnerSync, SyncPace}, AccessTime, KeyDate, KeyHash, KeyHashDate, KvEntry, ReadOp, ValueEntry, Weigher, WriteOp, }, + Policy, }; use crossbeam_channel::{Receiver, Sender, TrySendError}; @@ -185,16 +186,8 @@ where self.inner.set_valid_after(now); } - pub(crate) fn max_capacity(&self) -> Option { - self.inner.max_capacity() - } - - pub(crate) fn time_to_live(&self) -> Option { - self.inner.time_to_live() - } - - pub(crate) fn time_to_idle(&self) -> Option { - self.inner.time_to_idle() + pub(crate) fn policy(&self) -> Policy { + self.inner.policy() } #[cfg(test)] @@ -516,8 +509,8 @@ where .map(|(key, entry)| KvEntry::new(key, entry)) } - fn max_capacity(&self) -> Option { - self.max_capacity.map(|n| n as usize) + fn policy(&self) -> Policy { + Policy::new(self.max_capacity, 1, self.time_to_live, self.time_to_idle) } #[inline] diff --git a/src/dash/builder.rs b/src/dash/builder.rs index a06eb0ed..6b3504dc 100644 --- a/src/dash/builder.rs +++ b/src/dash/builder.rs @@ -33,7 +33,7 @@ use std::{ /// cache.insert(0, "zero"); /// /// // This get() will extend the entry life for another 5 minutes. -/// cache.get_if_present(&0); +/// cache.get(&0); /// /// // Even though we keep calling get(), the entry will expire /// // after 30 minutes (TTL) from the insert(). @@ -202,25 +202,27 @@ mod tests { fn build_cache() { // Cache let cache = CacheBuilder::new(100).build(); + let policy = cache.policy(); - assert_eq!(cache.max_capacity(), Some(100)); - assert_eq!(cache.time_to_live(), None); - assert_eq!(cache.time_to_idle(), None); + assert_eq!(policy.max_capacity(), Some(100)); + assert_eq!(policy.time_to_live(), None); + assert_eq!(policy.time_to_idle(), None); cache.insert('a', "Alice"); - assert_eq!(cache.get_if_present(&'a'), Some("Alice")); + assert_eq!(cache.get(&'a'), Some("Alice")); let cache = CacheBuilder::new(100) .time_to_live(Duration::from_secs(45 * 60)) .time_to_idle(Duration::from_secs(15 * 60)) .build(); + let policy = cache.policy(); - assert_eq!(cache.max_capacity(), Some(100)); - assert_eq!(cache.time_to_live(), Some(Duration::from_secs(45 * 60))); - assert_eq!(cache.time_to_idle(), Some(Duration::from_secs(15 * 60))); + assert_eq!(policy.max_capacity(), Some(100)); + assert_eq!(policy.time_to_live(), Some(Duration::from_secs(45 * 60))); + assert_eq!(policy.time_to_idle(), Some(Duration::from_secs(15 * 60))); cache.insert('a', "Alice"); - assert_eq!(cache.get_if_present(&'a'), Some("Alice")); + assert_eq!(cache.get(&'a'), Some("Alice")); } #[test] diff --git a/src/dash/cache.rs b/src/dash/cache.rs index 5d0f9887..d564b0b0 100644 --- a/src/dash/cache.rs +++ b/src/dash/cache.rs @@ -2,7 +2,10 @@ use super::{ base_cache::{BaseCache, HouseKeeperArc, MAX_SYNC_REPEATS, WRITE_RETRY_INTERVAL_MICROS}, CacheBuilder, ConcurrentCacheExt, Iter, }; -use crate::sync::{housekeeper::InnerSync, Weigher, WriteOp}; +use crate::{ + sync::{housekeeper::InnerSync, Weigher, WriteOp}, + Policy, +}; use crossbeam_channel::{Sender, TrySendError}; use std::{ @@ -17,20 +20,21 @@ use std::{ /// **Experimental**: A thread-safe concurrent in-memory cache built upon /// [`dashmap::DashMap`][dashmap]. /// -/// Unlike `sync` and `future` caches of Moka, `dash` cache does not provide full -/// concurrency of retrievals. This is because `DashMap` employs read-write locks on -/// internal shards. +/// The `dash::Cache` uses `DashMap` as the central key-value storage, while other +/// `sync` and `future` caches are using a lock-free concurrent hash table. +/// Since `DashMap` employs read-write locks on internal shards, it will have lower +/// concurrency on retrievals and updates than other caches. /// /// On the other hand, `dash` cache provides iterator, which returns immutable -/// references to the entries in a cache. +/// references to the entries in a cache. Other caches do not provide iterator. /// /// `dash` cache performs a best-effort bounding of the map using an entry /// replacement algorithm to determine which entries to evict when the capacity is /// exceeded. /// -/// To use this cache, enable a crate feature called "dash". Please note that the APIs -/// will _be changed very often_ in next few releases as this is yet an experimental -/// feature. +/// To use this cache, enable a crate feature called "dash" in your Cargo.toml. +/// Please note that the API of `dash` cache will _be changed very often_ in next few +/// releases as this is yet an experimental component. /// /// # Examples /// @@ -68,7 +72,7 @@ use std::{ /// for key in start..end { /// my_cache.insert(key, value(key)); /// // get() returns Option, a clone of the stored value. -/// assert_eq!(my_cache.get_if_present(&key), Some(value(key))); +/// assert_eq!(my_cache.get(&key), Some(value(key))); /// } /// /// // Invalidate every 4 element of the inserted entries. @@ -85,21 +89,21 @@ use std::{ /// // Verify the result. /// for key in 0..(NUM_THREADS * NUM_KEYS_PER_THREAD) { /// if key % 4 == 0 { -/// assert_eq!(cache.get_if_present(&key), None); +/// assert_eq!(cache.get(&key), None); /// } else { -/// assert_eq!(cache.get_if_present(&key), Some(value(key))); +/// assert_eq!(cache.get(&key), Some(value(key))); /// } /// } /// ``` /// -/// # Avoiding to clone the value at `get_if_present` +/// # Avoiding to clone the value at `get` /// -/// The return type of `get_if_present` method is `Option` instead of `Option<&V>`. -/// Every time `get_if_present` is called for an existing key, it creates a clone of -/// the stored value `V` and returns it. This is because the `Cache` allows -/// concurrent updates from threads so a value stored in the cache can be dropped or -/// replaced at any time by any other thread. `get` cannot return a reference `&V` as -/// it is impossible to guarantee the value outlives the reference. +/// The return type of `get` method is `Option` instead of `Option<&V>`. Every +/// time `get` is called for an existing key, it creates a clone of the stored value +/// `V` and returns it. This is because the `Cache` allows concurrent updates from +/// threads so a value stored in the cache can be dropped or replaced at any time by +/// any other thread. `get` cannot return a reference `&V` as it is impossible to +/// guarantee the value outlives the reference. /// /// If you want to store values that will be expensive to clone, wrap them by /// `std::sync::Arc` before storing in a cache. [`Arc`][rustdoc-std-arc] is a @@ -177,7 +181,7 @@ use std::{ /// cache.insert(0, "zero"); /// /// // This get() will extend the entry life for another 5 minutes. -/// cache.get_if_present(&0); +/// cache.get(&0); /// /// // Even though we keep calling get(), the entry will expire /// // after 30 minutes (TTL) from the insert(). @@ -282,8 +286,8 @@ where Self::with_everything(Some(max_capacity), None, build_hasher, None, None, None) } - /// Returns a [`CacheBuilder`][builder-struct], which can builds a `Cache` or - /// `SegmentedCache` with various configuration knobs. + /// Returns a [`CacheBuilder`][builder-struct], which can builds a `Cache` with + /// various configuration knobs. /// /// [builder-struct]: ./struct.CacheBuilder.html pub fn builder() -> CacheBuilder> { @@ -327,7 +331,7 @@ where /// on the borrowed form _must_ match those for the key type. /// /// [rustdoc-std-arc]: https://doc.rust-lang.org/stable/std/sync/struct.Arc.html - pub fn get_if_present(&self, key: &Q) -> Option + pub fn get(&self, key: &Q) -> Option where Arc: Borrow, Q: Hash + Eq + ?Sized, @@ -335,6 +339,17 @@ where self.base.get_with_hash(key, self.base.hash(key)) } + /// Deprecated, replaced with [`get`](#method.get) + #[doc(hidden)] + #[deprecated(since = "0.8.0", note = "Replaced with `get`")] + pub fn get_if_present(&self, key: &Q) -> Option + where + Arc: Borrow, + Q: Hash + Eq + ?Sized, + { + self.get(key) + } + /// Inserts a key-value pair into the cache. /// /// If the cache has this key present, the value is updated. @@ -380,19 +395,12 @@ where self.base.invalidate_all(); } - /// Returns the `max_capacity` of this cache. - pub fn max_capacity(&self) -> Option { - self.base.max_capacity() - } - - /// Returns the `time_to_live` of this cache. - pub fn time_to_live(&self) -> Option { - self.base.time_to_live() - } - - /// Returns the `time_to_idle` of this cache. - pub fn time_to_idle(&self) -> Option { - self.base.time_to_idle() + /// Returns a read-only cache policy of this cache. + /// + /// At this time, cache policy cannot be modified after cache creation. + /// A future version may support to modify it. + pub fn policy(&self) -> Policy { + self.base.policy() } #[cfg(test)] @@ -527,41 +535,41 @@ mod tests { cache.insert("a", "alice"); cache.insert("b", "bob"); - assert_eq!(cache.get_if_present(&"a"), Some("alice")); - assert_eq!(cache.get_if_present(&"b"), Some("bob")); + assert_eq!(cache.get(&"a"), Some("alice")); + assert_eq!(cache.get(&"b"), Some("bob")); cache.sync(); // counts: a -> 1, b -> 1 cache.insert("c", "cindy"); - assert_eq!(cache.get_if_present(&"c"), Some("cindy")); + assert_eq!(cache.get(&"c"), Some("cindy")); // counts: a -> 1, b -> 1, c -> 1 cache.sync(); - assert_eq!(cache.get_if_present(&"a"), Some("alice")); - assert_eq!(cache.get_if_present(&"b"), Some("bob")); + assert_eq!(cache.get(&"a"), Some("alice")); + assert_eq!(cache.get(&"b"), Some("bob")); cache.sync(); // counts: a -> 2, b -> 2, c -> 1 // "d" should not be admitted because its frequency is too low. cache.insert("d", "david"); // count: d -> 0 cache.sync(); - assert_eq!(cache.get_if_present(&"d"), None); // d -> 1 + assert_eq!(cache.get(&"d"), None); // d -> 1 cache.insert("d", "david"); cache.sync(); - assert_eq!(cache.get_if_present(&"d"), None); // d -> 2 + assert_eq!(cache.get(&"d"), None); // d -> 2 // "d" should be admitted and "c" should be evicted // because d's frequency is higher than c's. cache.insert("d", "dennis"); cache.sync(); - assert_eq!(cache.get_if_present(&"a"), Some("alice")); - assert_eq!(cache.get_if_present(&"b"), Some("bob")); - assert_eq!(cache.get_if_present(&"c"), None); - assert_eq!(cache.get_if_present(&"d"), Some("dennis")); + assert_eq!(cache.get(&"a"), Some("alice")); + assert_eq!(cache.get(&"b"), Some("bob")); + assert_eq!(cache.get(&"c"), None); + assert_eq!(cache.get(&"d"), Some("dennis")); cache.invalidate(&"b"); - assert_eq!(cache.get_if_present(&"b"), None); + assert_eq!(cache.get(&"b"), None); } #[test] @@ -583,18 +591,18 @@ mod tests { cache.insert("a", alice); cache.insert("b", bob); - assert_eq!(cache.get_if_present(&"a"), Some(alice)); - assert_eq!(cache.get_if_present(&"b"), Some(bob)); + assert_eq!(cache.get(&"a"), Some(alice)); + assert_eq!(cache.get(&"b"), Some(bob)); cache.sync(); // order (LRU -> MRU) and counts: a -> 1, b -> 1 cache.insert("c", cindy); - assert_eq!(cache.get_if_present(&"c"), Some(cindy)); + assert_eq!(cache.get(&"c"), Some(cindy)); // order and counts: a -> 1, b -> 1, c -> 1 cache.sync(); - assert_eq!(cache.get_if_present(&"a"), Some(alice)); - assert_eq!(cache.get_if_present(&"b"), Some(bob)); + assert_eq!(cache.get(&"a"), Some(alice)); + assert_eq!(cache.get(&"b"), Some(bob)); cache.sync(); // order and counts: c -> 1, a -> 2, b -> 2 @@ -603,41 +611,41 @@ mod tests { // of "a" and "c". cache.insert("d", david); // count: d -> 0 cache.sync(); - assert_eq!(cache.get_if_present(&"d"), None); // d -> 1 + assert_eq!(cache.get(&"d"), None); // d -> 1 cache.insert("d", david); cache.sync(); - assert_eq!(cache.get_if_present(&"d"), None); // d -> 2 + assert_eq!(cache.get(&"d"), None); // d -> 2 cache.insert("d", david); cache.sync(); - assert_eq!(cache.get_if_present(&"d"), None); // d -> 3 + assert_eq!(cache.get(&"d"), None); // d -> 3 cache.insert("d", david); cache.sync(); - assert_eq!(cache.get_if_present(&"d"), None); // d -> 4 + assert_eq!(cache.get(&"d"), None); // d -> 4 // Finally "d" should be admitted by evicting "c" and "a". cache.insert("d", dennis); cache.sync(); - assert_eq!(cache.get_if_present(&"a"), None); - assert_eq!(cache.get_if_present(&"b"), Some(bob)); - assert_eq!(cache.get_if_present(&"c"), None); - assert_eq!(cache.get_if_present(&"d"), Some(dennis)); + assert_eq!(cache.get(&"a"), None); + assert_eq!(cache.get(&"b"), Some(bob)); + assert_eq!(cache.get(&"c"), None); + assert_eq!(cache.get(&"d"), Some(dennis)); // Update "b" with "bill" (w: 15 -> 20). This should evict "d" (w: 15). cache.insert("b", bill); cache.sync(); - assert_eq!(cache.get_if_present(&"b"), Some(bill)); - assert_eq!(cache.get_if_present(&"d"), None); + assert_eq!(cache.get(&"b"), Some(bill)); + assert_eq!(cache.get(&"d"), None); // Re-add "a" (w: 10) and update "b" with "bob" (w: 20 -> 15). cache.insert("a", alice); cache.insert("b", bob); cache.sync(); - assert_eq!(cache.get_if_present(&"a"), Some(alice)); - assert_eq!(cache.get_if_present(&"b"), Some(bob)); - assert_eq!(cache.get_if_present(&"d"), None); + assert_eq!(cache.get(&"a"), Some(alice)); + assert_eq!(cache.get(&"b"), Some(bob)); + assert_eq!(cache.get(&"d"), None); // Verify the sizes. assert_eq!(cache.estimated_entry_count(), 2); @@ -656,7 +664,7 @@ mod tests { let cache = cache.clone(); std::thread::spawn(move || { cache.insert(10, format!("{}-100", id)); - cache.get_if_present(&10); + cache.get(&10); cache.insert(20, format!("{}-200", id)); cache.invalidate(&10); }) @@ -665,8 +673,8 @@ mod tests { handles.into_iter().for_each(|h| h.join().expect("Failed")); - assert!(cache.get_if_present(&10).is_none()); - assert!(cache.get_if_present(&20).is_some()); + assert!(cache.get(&10).is_none()); + assert!(cache.get(&20).is_some()); } #[test] @@ -680,9 +688,9 @@ mod tests { cache.insert("a", "alice"); cache.insert("b", "bob"); cache.insert("c", "cindy"); - assert_eq!(cache.get_if_present(&"a"), Some("alice")); - assert_eq!(cache.get_if_present(&"b"), Some("bob")); - assert_eq!(cache.get_if_present(&"c"), Some("cindy")); + assert_eq!(cache.get(&"a"), Some("alice")); + assert_eq!(cache.get(&"b"), Some("bob")); + assert_eq!(cache.get(&"c"), Some("cindy")); cache.sync(); cache.invalidate_all(); @@ -691,10 +699,10 @@ mod tests { cache.insert("d", "david"); cache.sync(); - assert!(cache.get_if_present(&"a").is_none()); - assert!(cache.get_if_present(&"b").is_none()); - assert!(cache.get_if_present(&"c").is_none()); - assert_eq!(cache.get_if_present(&"d"), Some("david")); + assert!(cache.get(&"a").is_none()); + assert!(cache.get(&"b").is_none()); + assert!(cache.get(&"c").is_none()); + assert_eq!(cache.get(&"d"), Some("david")); } #[test] @@ -718,12 +726,12 @@ mod tests { mock.increment(Duration::from_secs(5)); // 5 secs from the start. cache.sync(); - cache.get_if_present(&"a"); + cache.get(&"a"); mock.increment(Duration::from_secs(5)); // 10 secs. cache.sync(); - assert_eq!(cache.get_if_present(&"a"), None); + assert_eq!(cache.get(&"a"), None); assert!(cache.is_table_empty()); cache.insert("b", "bob"); @@ -734,7 +742,7 @@ mod tests { mock.increment(Duration::from_secs(5)); // 15 secs. cache.sync(); - assert_eq!(cache.get_if_present(&"b"), Some("bob")); + assert_eq!(cache.get(&"b"), Some("bob")); assert_eq!(cache.estimated_entry_count(), 1); cache.insert("b", "bill"); @@ -743,14 +751,14 @@ mod tests { mock.increment(Duration::from_secs(5)); // 20 secs cache.sync(); - assert_eq!(cache.get_if_present(&"b"), Some("bill")); + assert_eq!(cache.get(&"b"), Some("bill")); assert_eq!(cache.estimated_entry_count(), 1); mock.increment(Duration::from_secs(5)); // 25 secs cache.sync(); - assert_eq!(cache.get_if_present(&"a"), None); - assert_eq!(cache.get_if_present(&"b"), None); + assert_eq!(cache.get(&"a"), None); + assert_eq!(cache.get(&"b"), None); assert!(cache.is_table_empty()); } @@ -775,7 +783,7 @@ mod tests { mock.increment(Duration::from_secs(5)); // 5 secs from the start. cache.sync(); - assert_eq!(cache.get_if_present(&"a"), Some("alice")); + assert_eq!(cache.get(&"a"), Some("alice")); mock.increment(Duration::from_secs(5)); // 10 secs. cache.sync(); @@ -788,15 +796,15 @@ mod tests { mock.increment(Duration::from_secs(5)); // 15 secs. cache.sync(); - assert_eq!(cache.get_if_present(&"a"), None); - assert_eq!(cache.get_if_present(&"b"), Some("bob")); + assert_eq!(cache.get(&"a"), None); + assert_eq!(cache.get(&"b"), Some("bob")); assert_eq!(cache.estimated_entry_count(), 1); mock.increment(Duration::from_secs(10)); // 25 secs cache.sync(); - assert_eq!(cache.get_if_present(&"a"), None); - assert_eq!(cache.get_if_present(&"b"), None); + assert_eq!(cache.get(&"a"), None); + assert_eq!(cache.get(&"b"), None); assert!(cache.is_table_empty()); } diff --git a/src/future.rs b/src/future.rs index 61e9a928..0e4f151b 100644 --- a/src/future.rs +++ b/src/future.rs @@ -8,7 +8,7 @@ mod cache; mod value_initializer; pub use builder::CacheBuilder; -pub use cache::Cache; +pub use cache::{BlockingOp, Cache}; /// Provides extra methods that will be useful for testing. pub trait ConcurrentCacheExt { diff --git a/src/future/builder.rs b/src/future/builder.rs index d9262e83..034d9b1d 100644 --- a/src/future/builder.rs +++ b/src/future/builder.rs @@ -224,11 +224,12 @@ mod tests { async fn build_cache() { // Cache let cache = CacheBuilder::new(100).build(); + let policy = cache.policy(); - assert_eq!(cache.max_capacity(), Some(100)); - assert_eq!(cache.time_to_live(), None); - assert_eq!(cache.time_to_idle(), None); - assert_eq!(cache.num_segments(), 1); + assert_eq!(policy.max_capacity(), Some(100)); + assert_eq!(policy.time_to_live(), None); + assert_eq!(policy.time_to_idle(), None); + assert_eq!(policy.num_segments(), 1); cache.insert('a', "Alice").await; assert_eq!(cache.get(&'a'), Some("Alice")); @@ -237,11 +238,12 @@ mod tests { .time_to_live(Duration::from_secs(45 * 60)) .time_to_idle(Duration::from_secs(15 * 60)) .build(); + let policy = cache.policy(); - assert_eq!(cache.max_capacity(), Some(100)); - assert_eq!(cache.time_to_live(), Some(Duration::from_secs(45 * 60))); - assert_eq!(cache.time_to_idle(), Some(Duration::from_secs(15 * 60))); - assert_eq!(cache.num_segments(), 1); + assert_eq!(policy.max_capacity(), Some(100)); + assert_eq!(policy.time_to_live(), Some(Duration::from_secs(45 * 60))); + assert_eq!(policy.time_to_idle(), Some(Duration::from_secs(15 * 60))); + assert_eq!(policy.num_segments(), 1); cache.insert('a', "Alice").await; assert_eq!(cache.get(&'a'), Some("Alice")); diff --git a/src/future/cache.rs b/src/future/cache.rs index 4f6668b4..8e6b0c6a 100644 --- a/src/future/cache.rs +++ b/src/future/cache.rs @@ -8,7 +8,7 @@ use crate::{ housekeeper::InnerSync, PredicateId, Weigher, WriteOp, }, - PredicateError, + Policy, PredicateError, }; #[cfg(feature = "unstable-debug-counters")] @@ -43,7 +43,7 @@ use std::{ /// cache until either evicted or manually invalidated: /// /// - Inside an async context (`async fn` or `async` block), use -/// [`insert`](#method.insert), [`get_or_insert_with`](#method.get_or_insert_with) +/// [`insert`](#method.insert), [`get_with`](#method.get_with) /// or [`invalidate`](#method.invalidate) methods for updating the cache and `await` /// them. /// - Outside any async context, use [`blocking_insert`](#method.blocking_insert) or @@ -120,8 +120,8 @@ use std::{ /// /// If you want to atomically initialize and insert a value when the key is not /// present, you might want to check other insertion methods -/// [`get_or_insert_with`](#method.get_or_insert_with) and -/// [`get_or_try_insert_with`](#method.get_or_try_insert_with). +/// [`get_with`](#method.get_with) and +/// [`try_get_with`](#method.try_get_with). /// /// # Avoiding to clone the value at `get` /// @@ -380,6 +380,22 @@ where self.base.get_with_hash(key, self.base.hash(key)) } + /// Deprecated, replaced with [`get_with`](#method.get_with) + #[deprecated(since = "0.8.0", note = "Replaced with `get_with`")] + pub async fn get_or_insert_with(&self, key: K, init: impl Future) -> V { + self.get_with(key, init).await + } + + /// Deprecated, replaced with [`try_get_with`](#method.try_get_with) + #[deprecated(since = "0.8.0", note = "Replaced with `try_get_with`")] + pub async fn get_or_try_insert_with(&self, key: K, init: F) -> Result> + where + F: Future>, + E: Send + Sync + 'static, + { + self.try_get_with(key, init).await + } + /// 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. /// @@ -413,10 +429,10 @@ where /// println!("Task {} started.", task_id); /// /// // Insert and get the value for key1. Although all four async tasks - /// // will call `get_or_insert_with` at the same time, the `init` async + /// // will call `get_with` at the same time, the `init` async /// // block must be resolved only once. /// let value = my_cache - /// .get_or_insert_with("key1", async move { + /// .get_with("key1", async move { /// println!("Task {} inserting a value.", task_id); /// Arc::new(vec![0u8; TEN_MIB]) /// }) @@ -461,10 +477,7 @@ where /// 0, 1 and 2 above), this method will restart and resolve one of the remaining /// `init` futures. /// - pub async fn get_or_insert_with(&self, key: K, init: F) -> V - where - F: Future, - { + pub async fn get_with(&self, key: K, init: impl Future) -> V { let hash = self.base.hash(&key); let key = Arc::new(key); self.get_or_insert_with_hash_and_fun(key, hash, init).await @@ -509,10 +522,10 @@ where /// println!("Task {} started.", task_id); /// /// // Try to insert and get the value for key1. Although - /// // all four async tasks will call `get_or_try_insert_with` + /// // all four async tasks will call `try_get_with` /// // at the same time, get_html() must be called only once. /// let value = my_cache - /// .get_or_try_insert_with( + /// .try_get_with( /// "key1", /// get_html(task_id, "https://www.rust-lang.org"), /// ).await; @@ -560,7 +573,7 @@ where /// 0, 1 and 3 above), this method will restart and resolve one of the remaining /// `init` futures. /// - pub async fn get_or_try_insert_with(&self, key: K, init: F) -> Result> + pub async fn try_get_with(&self, key: K, init: F) -> Result> where F: Future>, E: Send + Sync + 'static, @@ -580,11 +593,7 @@ where self.insert_with_hash(key, hash, value).await } - /// Blocking [insert](#method.insert) to call outside of asynchronous contexts. - /// - /// This method is intended for use cases where you are inserting from - /// synchronous code. - pub fn blocking_insert(&self, key: K, value: V) { + fn do_blocking_insert(&self, key: K, value: V) { let hash = self.base.hash(&key); let key = Arc::new(key); let op = self.base.do_insert_with_hash(key, hash, value); @@ -611,12 +620,7 @@ where } } - /// Blocking [invalidate](#method.invalidate) to call outside of asynchronous - /// contexts. - /// - /// This method is intended for use cases where you are invalidating from - /// synchronous code. - pub fn blocking_invalidate(&self, key: &Q) + fn do_blocking_invalidate(&self, key: &Q) where Arc: Borrow, Q: Hash + Eq + ?Sized, @@ -676,26 +680,19 @@ where self.base.invalidate_entries_if(Arc::new(predicate)) } - /// Returns the `max_capacity` of this cache. - pub fn max_capacity(&self) -> Option { - self.base.max_capacity() + /// Returns a `BlockingOp` for this cache. It provides blocking + /// [insert](#method.insert) and [invalidate](#method.invalidate) methods, which + /// can be called outside of asynchronous contexts. + pub fn blocking(&self) -> BlockingOp<'_, K, V, S> { + BlockingOp(self) } - /// Returns the `time_to_live` of this cache. - pub fn time_to_live(&self) -> Option { - self.base.time_to_live() - } - - /// Returns the `time_to_idle` of this cache. - pub fn time_to_idle(&self) -> Option { - self.base.time_to_idle() - } - - /// Returns the number of internal segments of this cache. + /// Returns a read-only cache policy of this cache. /// - /// `Cache` always returns `1`. - pub fn num_segments(&self) -> usize { - 1 + /// At this time, cache policy cannot be modified after cache creation. + /// A future version may support to modify it. + pub fn policy(&self) -> Policy { + self.base.policy() } #[cfg(feature = "unstable-debug-counters")] @@ -872,6 +869,37 @@ where } } +pub struct BlockingOp<'a, K, V, S>(&'a Cache); + +impl<'a, K, V, S> BlockingOp<'a, K, V, S> +where + K: Hash + Eq + Send + Sync + 'static, + V: Clone + Send + Sync + 'static, + S: BuildHasher + Clone + Send + Sync + 'static, +{ + /// Blocking [insert](./struct.Cache.html#method.insert) to call outside of + /// asynchronous contexts. + /// + /// This method is intended for use cases where you are inserting from + /// synchronous code. + pub fn insert(&self, key: K, value: V) { + self.0.do_blocking_insert(key, value) + } + + /// Blocking [invalidate](./struct.Cache.html#method.invalidate) to call outside + /// of asynchronous contexts. + /// + /// This method is intended for use cases where you are invalidating from + /// synchronous code. + pub fn invalidate(&self, key: &Q) + where + Arc: Borrow, + Q: Hash + Eq + ?Sized, + { + self.0.do_blocking_invalidate(key) + } +} + // To see the debug prints, run test as `cargo test -- --nocapture` #[cfg(test)] mod tests { @@ -936,14 +964,14 @@ mod tests { // Make the cache exterior immutable. let cache = cache; - cache.blocking_insert("a", "alice"); - cache.blocking_insert("b", "bob"); + cache.blocking().insert("a", "alice"); + cache.blocking().insert("b", "bob"); assert_eq!(cache.get(&"a"), Some("alice")); assert_eq!(cache.get(&"b"), Some("bob")); cache.sync(); // counts: a -> 1, b -> 1 - cache.blocking_insert("c", "cindy"); + cache.blocking().insert("c", "cindy"); assert_eq!(cache.get(&"c"), Some("cindy")); // counts: a -> 1, b -> 1, c -> 1 cache.sync(); @@ -954,24 +982,24 @@ mod tests { // counts: a -> 2, b -> 2, c -> 1 // "d" should not be admitted because its frequency is too low. - cache.blocking_insert("d", "david"); // count: d -> 0 + cache.blocking().insert("d", "david"); // count: d -> 0 cache.sync(); assert_eq!(cache.get(&"d"), None); // d -> 1 - cache.blocking_insert("d", "david"); + cache.blocking().insert("d", "david"); cache.sync(); assert_eq!(cache.get(&"d"), None); // d -> 2 // "d" should be admitted and "c" should be evicted // because d's frequency is higher than c's. - cache.blocking_insert("d", "dennis"); + cache.blocking().insert("d", "dennis"); cache.sync(); assert_eq!(cache.get(&"a"), Some("alice")); assert_eq!(cache.get(&"b"), Some("bob")); assert_eq!(cache.get(&"c"), None); assert_eq!(cache.get(&"d"), Some("dennis")); - cache.blocking_invalidate(&"b"); + cache.blocking().invalidate(&"b"); assert_eq!(cache.get(&"b"), None); } @@ -1065,10 +1093,10 @@ mod tests { let cache = cache.clone(); if id == 0 { tokio::spawn(async move { - cache.blocking_insert(10, format!("{}-100", id)); + cache.blocking().insert(10, format!("{}-100", id)); cache.get(&10); - cache.blocking_insert(20, format!("{}-200", id)); - cache.blocking_invalidate(&10); + cache.blocking().insert(20, format!("{}-200", id)); + cache.blocking().invalidate(&10); }) } else { tokio::spawn(async move { @@ -1289,21 +1317,21 @@ mod tests { } #[tokio::test] - async fn get_or_insert_with() { + async fn get_with() { let cache = Cache::new(100); const KEY: u32 = 0; // This test will run five async tasks: // - // Task1 will be the first task to call `get_or_insert_with` for a key, so + // 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 { - // Call `get_or_insert_with` immediately. + // Call `get_with` immediately. let v = cache1 - .get_or_insert_with(KEY, async { + .get_with(KEY, async { // Wait for 300 ms and return a &str value. Timer::after(Duration::from_millis(300)).await; "task1" @@ -1313,22 +1341,20 @@ mod tests { } }; - // Task2 will be the second task to call `get_or_insert_with` for the same + // 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 { - // Wait for 100 ms before calling `get_or_insert_with`. + // Wait for 100 ms before calling `get_with`. Timer::after(Duration::from_millis(100)).await; - let v = cache2 - .get_or_insert_with(KEY, async { unreachable!() }) - .await; + let v = cache2.get_with(KEY, async { unreachable!() }).await; assert_eq!(v, "task1"); } }; - // Task3 will be the third task to call `get_or_insert_with` for the same + // 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 @@ -1336,11 +1362,9 @@ mod tests { let task3 = { let cache3 = cache.clone(); async move { - // Wait for 400 ms before calling `get_or_insert_with`. + // Wait for 400 ms before calling `get_with`. Timer::after(Duration::from_millis(400)).await; - let v = cache3 - .get_or_insert_with(KEY, async { unreachable!() }) - .await; + let v = cache3.get_with(KEY, async { unreachable!() }).await; assert_eq!(v, "task1"); } }; @@ -1373,7 +1397,7 @@ mod tests { } #[tokio::test] - async fn get_or_try_insert_with() { + async fn try_get_with() { use std::sync::Arc; // Note that MyError does not implement std::error::Error trait @@ -1388,15 +1412,15 @@ mod tests { // This test will run eight async tasks: // - // Task1 will be the first task to call `get_or_insert_with` for a key, so + // 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 { - // Call `get_or_try_insert_with` immediately. + // Call `try_get_with` immediately. let v = cache1 - .get_or_try_insert_with(KEY, async { + .try_get_with(KEY, async { // Wait for 300 ms and return an error. Timer::after(Duration::from_millis(300)).await; Err(MyError("task1 error".into())) @@ -1406,23 +1430,21 @@ mod tests { } }; - // Task2 will be the second task to call `get_or_insert_with` for the same + // 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 { - // Wait for 100 ms before calling `get_or_try_insert_with`. + // Wait for 100 ms before calling `try_get_with`. Timer::after(Duration::from_millis(100)).await; - let v: MyResult<_> = cache2 - .get_or_try_insert_with(KEY, async { unreachable!() }) - .await; + let v: MyResult<_> = cache2.try_get_with(KEY, async { unreachable!() }).await; assert!(v.is_err()); } }; - // Task3 will be the third task to call `get_or_insert_with` for the same + // 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 @@ -1430,10 +1452,10 @@ mod tests { let task3 = { let cache3 = cache.clone(); async move { - // Wait for 400 ms before calling `get_or_try_insert_with`. + // Wait for 400 ms before calling `try_get_with`. Timer::after(Duration::from_millis(400)).await; let v: MyResult<_> = cache3 - .get_or_try_insert_with(KEY, async { + .try_get_with(KEY, async { // Wait for 300 ms and return an Ok(&str) value. Timer::after(Duration::from_millis(300)).await; Ok("task3") @@ -1443,22 +1465,20 @@ mod tests { } }; - // Task4 will be the fourth task to call `get_or_insert_with` for the same + // 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 { - // Wait for 500 ms before calling `get_or_try_insert_with`. + // Wait for 500 ms before calling `try_get_with`. Timer::after(Duration::from_millis(500)).await; - let v: MyResult<_> = cache4 - .get_or_try_insert_with(KEY, async { unreachable!() }) - .await; + let v: MyResult<_> = cache4.try_get_with(KEY, async { unreachable!() }).await; assert_eq!(v.unwrap(), "task3"); } }; - // Task5 will be the fifth task to call `get_or_insert_with` for the same + // 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 @@ -1466,11 +1486,9 @@ mod tests { let task5 = { let cache5 = cache.clone(); async move { - // Wait for 800 ms before calling `get_or_try_insert_with`. + // Wait for 800 ms before calling `try_get_with`. Timer::after(Duration::from_millis(800)).await; - let v: MyResult<_> = cache5 - .get_or_try_insert_with(KEY, async { unreachable!() }) - .await; + let v: MyResult<_> = cache5.try_get_with(KEY, async { unreachable!() }).await; assert_eq!(v.unwrap(), "task3"); } }; @@ -1516,7 +1534,7 @@ mod tests { #[tokio::test] // https://github.com/moka-rs/moka/issues/43 - async fn handle_panic_in_get_or_insert_with() { + async fn handle_panic_in_get_with() { use tokio::time::{sleep, Duration}; let cache = Cache::new(16); @@ -1526,21 +1544,21 @@ mod tests { let semaphore_ref = semaphore.clone(); tokio::task::spawn(async move { let _ = cache_ref - .get_or_insert_with(1, async move { + .get_with(1, async move { semaphore_ref.add_permits(1); sleep(Duration::from_millis(50)).await; - panic!("Panic during get_or_try_insert_with"); + panic!("Panic during try_get_with"); }) .await; }); } let _ = semaphore.acquire().await.expect("semaphore acquire failed"); - assert_eq!(cache.get_or_insert_with(1, async { 5 }).await, 5); + assert_eq!(cache.get_with(1, async { 5 }).await, 5); } #[tokio::test] // https://github.com/moka-rs/moka/issues/43 - async fn handle_panic_in_get_or_try_insert_with() { + async fn handle_panic_in_try_get_with() { use tokio::time::{sleep, Duration}; let cache = Cache::new(16); @@ -1550,24 +1568,24 @@ mod tests { let semaphore_ref = semaphore.clone(); tokio::task::spawn(async move { let _ = cache_ref - .get_or_try_insert_with(1, async move { + .try_get_with(1, async move { semaphore_ref.add_permits(1); sleep(Duration::from_millis(50)).await; - panic!("Panic during get_or_try_insert_with"); + panic!("Panic during try_get_with"); }) .await as Result<_, Arc>; }); } let _ = semaphore.acquire().await.expect("semaphore acquire failed"); assert_eq!( - cache.get_or_try_insert_with(1, async { Ok(5) }).await as Result<_, Arc>, + cache.try_get_with(1, async { Ok(5) }).await as Result<_, Arc>, Ok(5) ); } #[tokio::test] // https://github.com/moka-rs/moka/issues/59 - async fn abort_get_or_insert_with() { + async fn abort_get_with() { use tokio::time::{sleep, Duration}; let cache = Cache::new(16); @@ -1580,7 +1598,7 @@ mod tests { handle = tokio::task::spawn(async move { let _ = cache_ref - .get_or_insert_with(1, async move { + .get_with(1, async move { semaphore_ref.add_permits(1); sleep(Duration::from_millis(50)).await; unreachable!(); @@ -1592,12 +1610,12 @@ mod tests { let _ = semaphore.acquire().await.expect("semaphore acquire failed"); handle.abort(); - assert_eq!(cache.get_or_insert_with(1, async { 5 }).await, 5); + assert_eq!(cache.get_with(1, async { 5 }).await, 5); } #[tokio::test] // https://github.com/moka-rs/moka/issues/59 - async fn abort_get_or_try_insert_with() { + async fn abort_try_get_with() { use tokio::time::{sleep, Duration}; let cache = Cache::new(16); @@ -1610,7 +1628,7 @@ mod tests { handle = tokio::task::spawn(async move { let _ = cache_ref - .get_or_try_insert_with(1, async move { + .try_get_with(1, async move { semaphore_ref.add_permits(1); sleep(Duration::from_millis(50)).await; unreachable!(); @@ -1623,7 +1641,7 @@ mod tests { handle.abort(); assert_eq!( - cache.get_or_try_insert_with(1, async { Ok(5) }).await as Result<_, Arc>, + cache.try_get_with(1, async { Ok(5) }).await as Result<_, Arc>, Ok(5) ); } diff --git a/src/future/value_initializer.rs b/src/future/value_initializer.rs index 461c8b69..e6045bf2 100644 --- a/src/future/value_initializer.rs +++ b/src/future/value_initializer.rs @@ -214,8 +214,8 @@ where // Retry from the beginning. continue; } - // Somebody else (a future containing `get_or_insert_with`/ - // `get_or_try_insert_with`) has been aborted. + // Somebody else (a future containing `get_with`/`try_get_with`) + // has been aborted. WaiterValue::EnclosingFutureAborted => { retries += 1; panic_if_retry_exhausted_for_aborting(retries, MAX_RETRIES); @@ -273,8 +273,8 @@ fn panic_if_retry_exhausted_for_aborting(retries: usize, max: usize) { if retries >= max { panic!( "Too many retries. Tried to read the return value from the `init` future \ - but failed {} times. Maybe the future containing `get_or_insert_with`/\ - `get_or_try_insert_with` kept being aborted?", + but failed {} times. Maybe the future containing `get_with`/`try_get_with` \ + kept being aborted?", retries ); } diff --git a/src/lib.rs b/src/lib.rs index 428941a9..fa86421b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -165,8 +165,10 @@ pub mod unsync; pub(crate) mod cht; pub(crate) mod common; +pub(crate) mod policy; pub use common::error::PredicateError; +pub use policy::Policy; #[cfg(feature = "unstable-debug-counters")] pub use sync::debug_counters::GlobalDebugCounters; diff --git a/src/policy.rs b/src/policy.rs new file mode 100644 index 00000000..4bd8fa6c --- /dev/null +++ b/src/policy.rs @@ -0,0 +1,54 @@ +use std::time::Duration; + +#[derive(Clone, Debug)] +/// The policy of a cache. +pub struct Policy { + max_capacity: Option, + num_segments: usize, + time_to_live: Option, + time_to_idle: Option, +} + +impl Policy { + pub(crate) fn new( + max_capacity: Option, + num_segments: usize, + time_to_live: Option, + time_to_idle: Option, + ) -> Self { + Self { + max_capacity, + num_segments, + time_to_live, + time_to_idle, + } + } + + /// Returns the `max_capacity` of the cache. + pub fn max_capacity(&self) -> Option { + self.max_capacity + } + + pub(crate) fn set_max_capacity(&mut self, capacity: Option) { + self.max_capacity = capacity; + } + + /// Returns the number of internal segments of the cache. + pub fn num_segments(&self) -> usize { + self.num_segments + } + + pub(crate) fn set_num_segments(&mut self, num: usize) { + self.num_segments = num; + } + + /// Returns the `time_to_live` of the cache. + pub fn time_to_live(&self) -> Option { + self.time_to_live + } + + /// Returns the `time_to_idle` of the cache. + pub fn time_to_idle(&self) -> Option { + self.time_to_idle + } +} diff --git a/src/sync/base_cache.rs b/src/sync/base_cache.rs index db5f890a..4454addf 100644 --- a/src/sync/base_cache.rs +++ b/src/sync/base_cache.rs @@ -19,7 +19,7 @@ use crate::{ time::{CheckedTimeOps, Clock, Instant}, CacheRegion, }, - PredicateError, + Policy, PredicateError, }; use crossbeam_channel::{Receiver, Sender, TrySendError}; use crossbeam_utils::atomic::AtomicCell; @@ -208,16 +208,8 @@ where self.inner.register_invalidation_predicate(predicate, now) } - pub(crate) fn max_capacity(&self) -> Option { - self.inner.max_capacity() - } - - pub(crate) fn time_to_live(&self) -> Option { - self.inner.time_to_live() - } - - pub(crate) fn time_to_idle(&self) -> Option { - self.inner.time_to_idle() + pub(crate) fn policy(&self) -> Policy { + self.inner.policy() } #[cfg(feature = "unstable-debug-counters")] @@ -583,8 +575,8 @@ where .map(|(key, entry)| KvEntry::new(key, entry)) } - fn max_capacity(&self) -> Option { - self.max_capacity.map(|n| n as usize) + fn policy(&self) -> Policy { + Policy::new(self.max_capacity, 1, self.time_to_live, self.time_to_idle) } #[inline] diff --git a/src/sync/builder.rs b/src/sync/builder.rs index 1be5fb19..d5753efd 100644 --- a/src/sync/builder.rs +++ b/src/sync/builder.rs @@ -305,11 +305,12 @@ mod tests { fn build_cache() { // Cache let cache = CacheBuilder::new(100).build(); + let policy = cache.policy(); - assert_eq!(cache.max_capacity(), Some(100)); - assert_eq!(cache.time_to_live(), None); - assert_eq!(cache.time_to_idle(), None); - assert_eq!(cache.num_segments(), 1); + assert_eq!(policy.max_capacity(), Some(100)); + assert_eq!(policy.time_to_live(), None); + assert_eq!(policy.time_to_idle(), None); + assert_eq!(policy.num_segments(), 1); cache.insert('a', "Alice"); assert_eq!(cache.get(&'a'), Some("Alice")); @@ -318,11 +319,12 @@ mod tests { .time_to_live(Duration::from_secs(45 * 60)) .time_to_idle(Duration::from_secs(15 * 60)) .build(); + let config = cache.policy(); - assert_eq!(cache.max_capacity(), Some(100)); - assert_eq!(cache.time_to_live(), Some(Duration::from_secs(45 * 60))); - assert_eq!(cache.time_to_idle(), Some(Duration::from_secs(15 * 60))); - assert_eq!(cache.num_segments(), 1); + assert_eq!(config.max_capacity(), Some(100)); + assert_eq!(config.time_to_live(), Some(Duration::from_secs(45 * 60))); + assert_eq!(config.time_to_idle(), Some(Duration::from_secs(15 * 60))); + assert_eq!(config.num_segments(), 1); cache.insert('a', "Alice"); assert_eq!(cache.get(&'a'), Some("Alice")); @@ -332,11 +334,12 @@ mod tests { fn build_segmented_cache() { // SegmentCache let cache = CacheBuilder::new(100).segments(16).build(); + let policy = cache.policy(); - assert_eq!(cache.max_capacity(), Some(100)); - assert_eq!(cache.time_to_live(), None); - assert_eq!(cache.time_to_idle(), None); - assert_eq!(cache.num_segments(), 16_usize.next_power_of_two()); + assert_eq!(policy.max_capacity(), Some(100)); + assert_eq!(policy.time_to_live(), None); + assert_eq!(policy.time_to_idle(), None); + assert_eq!(policy.num_segments(), 16_usize.next_power_of_two()); cache.insert('b', "Bob"); assert_eq!(cache.get(&'b'), Some("Bob")); @@ -346,11 +349,12 @@ mod tests { .time_to_live(Duration::from_secs(45 * 60)) .time_to_idle(Duration::from_secs(15 * 60)) .build(); + let policy = cache.policy(); - assert_eq!(cache.max_capacity(), Some(100)); - assert_eq!(cache.time_to_live(), Some(Duration::from_secs(45 * 60))); - assert_eq!(cache.time_to_idle(), Some(Duration::from_secs(15 * 60))); - assert_eq!(cache.num_segments(), 16_usize.next_power_of_two()); + assert_eq!(policy.max_capacity(), Some(100)); + assert_eq!(policy.time_to_live(), Some(Duration::from_secs(45 * 60))); + assert_eq!(policy.time_to_idle(), Some(Duration::from_secs(15 * 60))); + assert_eq!(policy.num_segments(), 16_usize.next_power_of_two()); cache.insert('b', "Bob"); assert_eq!(cache.get(&'b'), Some("Bob")); diff --git a/src/sync/cache.rs b/src/sync/cache.rs index 7d17ba49..fa5e6374 100644 --- a/src/sync/cache.rs +++ b/src/sync/cache.rs @@ -4,7 +4,7 @@ use super::{ value_initializer::ValueInitializer, CacheBuilder, ConcurrentCacheExt, PredicateId, Weigher, WriteOp, }; -use crate::{sync::value_initializer::InitResult, PredicateError}; +use crate::{sync::value_initializer::InitResult, Policy, PredicateError}; use crossbeam_channel::{Sender, TrySendError}; use std::{ @@ -29,7 +29,7 @@ use std::{ /// # Examples /// /// Cache entries are manually added using [`insert`](#method.insert) or -/// [`get_or_insert_with`](#method.get_or_insert_with) methods, and are stored in +/// [`get_with`](#method.get_with) methods, and are stored in /// the cache until either evicted or manually invalidated. /// /// Here's an example of reading and updating a cache by using multiple threads: @@ -89,8 +89,8 @@ use std::{ /// /// If you want to atomically initialize and insert a value when the key is not /// present, you might want to check other insertion methods -/// [`get_or_insert_with`](#method.get_or_insert_with) and -/// [`get_or_try_insert_with`](#method.get_or_try_insert_with). +/// [`get_with`](#method.get_with) and +/// [`try_get_with`](#method.try_get_with). /// /// # Avoiding to clone the value at `get` /// @@ -337,6 +337,22 @@ where self.base.get_with_hash(key, hash) } + /// Deprecated, replaced with [`get_with`](#method.get_with) + #[deprecated(since = "0.8.0", note = "Replaced with `get_with`")] + pub fn get_or_insert_with(&self, key: K, init: impl FnOnce() -> V) -> V { + self.get_with(key, init) + } + + /// Deprecated, replaced with [`try_get_with`](#method.try_get_with) + #[deprecated(since = "0.8.0", note = "Replaced with `try_get_with`")] + pub fn get_or_try_insert_with(&self, key: K, init: F) -> Result> + where + F: FnOnce() -> Result, + E: Send + Sync + 'static, + { + self.try_get_with(key, init) + } + /// Ensures the value of the key exists by inserting the result of the init /// function if not exist, and returns a _clone_ of the value. /// @@ -362,9 +378,9 @@ where /// println!("Thread {} started.", task_id); /// /// // Try to insert and get the value for key1. Although all four - /// // threads will call `get_or_insert_with` at the same time, the - /// // `init` closure must be evaluated only once. - /// let value = my_cache.get_or_insert_with("key1", || { + /// // threads will call `get_with` at the same time, the `init` closure + /// // must be evaluated only once. + /// let value = my_cache.get_with("key1", || { /// println!("Thread {} inserting a value.", task_id); /// Arc::new(vec![0u8; TEN_MIB]) /// }); @@ -410,7 +426,7 @@ where /// progress (e.g. thread 0, 2 and 3 above), this method will restart and resolve /// one of the remaining `init` closure. /// - pub fn get_or_insert_with(&self, key: K, init: impl FnOnce() -> V) -> V { + pub fn get_with(&self, key: K, init: impl FnOnce() -> V) -> V { let hash = self.base.hash(&key); let key = Arc::new(key); self.get_or_insert_with_hash_and_fun(key, hash, init) @@ -469,9 +485,9 @@ where /// println!("Thread {} started.", thread_id); /// /// // Try to insert and get the value for key1. Although all four - /// // threads will call `get_or_try_insert_with` at the same time, + /// // threads will call `try_get_with` at the same time, /// // get_file_size() must be called only once. - /// let value = my_cache.get_or_try_insert_with( + /// let value = my_cache.try_get_with( /// "key1", /// || get_file_size(thread_id, "./Cargo.toml"), /// ); @@ -521,7 +537,7 @@ where /// progress (e.g. thread 0, 2 and 3 above), this method will restart and resolve /// one of the remaining `init` closure. /// - pub fn get_or_try_insert_with(&self, key: K, init: F) -> Result> + pub fn try_get_with(&self, key: K, init: F) -> Result> where F: FnOnce() -> Result, E: Send + Sync + 'static, @@ -656,26 +672,12 @@ where self.base.invalidate_entries_if(predicate) } - /// Returns the `max_capacity` of this cache. - pub fn max_capacity(&self) -> Option { - self.base.max_capacity() - } - - /// Returns the `time_to_live` of this cache. - pub fn time_to_live(&self) -> Option { - self.base.time_to_live() - } - - /// Returns the `time_to_idle` of this cache. - pub fn time_to_idle(&self) -> Option { - self.base.time_to_idle() - } - - /// Returns the number of internal segments of this cache. + /// Returns a read-only cache policy of this cache. /// - /// `Cache` always returns `1`. - pub fn num_segments(&self) -> usize { - 1 + /// At this time, cache policy cannot be modified after cache creation. + /// A future version may support to modify it. + pub fn policy(&self) -> Policy { + self.base.policy() } #[cfg(test)] @@ -1121,7 +1123,7 @@ mod tests { } #[test] - fn get_or_insert_with() { + fn get_with() { use std::thread::{sleep, spawn}; let cache = Cache::new(100); @@ -1129,14 +1131,14 @@ mod tests { // This test will run five threads: // - // Thread1 will be the first thread to call `get_or_insert_with` for a key, so + // Thread1 will be the first thread to call `get_with` for a key, so // its async block will be evaluated and then a &str value "thread1" will be // inserted to the cache. let thread1 = { let cache1 = cache.clone(); spawn(move || { - // Call `get_or_insert_with` immediately. - let v = cache1.get_or_insert_with(KEY, || { + // Call `get_with` immediately. + let v = cache1.get_with(KEY, || { // Wait for 300 ms and return a &str value. sleep(Duration::from_millis(300)); "thread1" @@ -1145,20 +1147,20 @@ mod tests { }) }; - // Thread2 will be the second thread to call `get_or_insert_with` for the same + // Thread2 will be the second thread to call `get_with` for the same // key, so its async block will not be evaluated. Once thread1's async block // finishes, it will get the value inserted by thread1's async block. let thread2 = { let cache2 = cache.clone(); spawn(move || { - // Wait for 100 ms before calling `get_or_insert_with`. + // Wait for 100 ms before calling `get_with`. sleep(Duration::from_millis(100)); - let v = cache2.get_or_insert_with(KEY, || unreachable!()); + let v = cache2.get_with(KEY, || unreachable!()); assert_eq!(v, "thread1"); }) }; - // Thread3 will be the third thread to call `get_or_insert_with` for the same + // Thread3 will be the third thread to call `get_with` for the same // key. By the time it calls, thread1'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 thread1's @@ -1166,9 +1168,9 @@ mod tests { let thread3 = { let cache3 = cache.clone(); spawn(move || { - // Wait for 400 ms before calling `get_or_insert_with`. + // Wait for 400 ms before calling `get_with`. sleep(Duration::from_millis(400)); - let v = cache3.get_or_insert_with(KEY, || unreachable!()); + let v = cache3.get_with(KEY, || unreachable!()); assert_eq!(v, "thread1"); }) }; @@ -1203,7 +1205,7 @@ mod tests { } #[test] - fn get_or_try_insert_with() { + fn try_get_with() { use std::{ sync::Arc, thread::{sleep, spawn}, @@ -1221,14 +1223,14 @@ mod tests { // This test will run eight async threads: // - // Thread1 will be the first thread to call `get_or_insert_with` for a key, so + // Thread1 will be the first thread to call `try_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 thread1 = { let cache1 = cache.clone(); spawn(move || { - // Call `get_or_try_insert_with` immediately. - let v = cache1.get_or_try_insert_with(KEY, || { + // Call `try_get_with` immediately. + let v = cache1.try_get_with(KEY, || { // Wait for 300 ms and return an error. sleep(Duration::from_millis(300)); Err(MyError("thread1 error".into())) @@ -1237,21 +1239,21 @@ mod tests { }) }; - // Thread2 will be the second thread to call `get_or_insert_with` for the same + // Thread2 will be the second thread to call `try_get_with` for the same // key, so its async block will not be evaluated. Once thread1's async block // finishes, it will get the same error value returned by thread1's async // block. let thread2 = { let cache2 = cache.clone(); spawn(move || { - // Wait for 100 ms before calling `get_or_try_insert_with`. + // Wait for 100 ms before calling `try_get_with`. sleep(Duration::from_millis(100)); - let v: MyResult<_> = cache2.get_or_try_insert_with(KEY, || unreachable!()); + let v: MyResult<_> = cache2.try_get_with(KEY, || unreachable!()); assert!(v.is_err()); }) }; - // Thread3 will be the third thread to call `get_or_insert_with` for the same + // Thread3 will be the third thread to call `get_with` for the same // key. By the time it calls, thread1'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 @@ -1259,9 +1261,9 @@ mod tests { let thread3 = { let cache3 = cache.clone(); spawn(move || { - // Wait for 400 ms before calling `get_or_try_insert_with`. + // Wait for 400 ms before calling `try_get_with`. sleep(Duration::from_millis(400)); - let v: MyResult<_> = cache3.get_or_try_insert_with(KEY, || { + let v: MyResult<_> = cache3.try_get_with(KEY, || { // Wait for 300 ms and return an Ok(&str) value. sleep(Duration::from_millis(300)); Ok("thread3") @@ -1270,20 +1272,20 @@ mod tests { }) }; - // thread4 will be the fourth thread to call `get_or_insert_with` for the same + // thread4 will be the fourth thread to call `try_get_with` for the same // key. So its async block will not be evaluated. Once thread3's async block // finishes, it will get the same okay &str value. let thread4 = { let cache4 = cache.clone(); spawn(move || { - // Wait for 500 ms before calling `get_or_try_insert_with`. + // Wait for 500 ms before calling `try_get_with`. sleep(Duration::from_millis(500)); - let v: MyResult<_> = cache4.get_or_try_insert_with(KEY, || unreachable!()); + let v: MyResult<_> = cache4.try_get_with(KEY, || unreachable!()); assert_eq!(v.unwrap(), "thread3"); }) }; - // Thread5 will be the fifth thread to call `get_or_insert_with` for the same + // Thread5 will be the fifth thread to call `try_get_with` for the same // key. So its async block will not be evaluated. By the time it calls, // thread3's async block should have finished already, so its async block will // not be evaluated and will get the value insert by thread3's async block @@ -1291,9 +1293,9 @@ mod tests { let thread5 = { let cache5 = cache.clone(); spawn(move || { - // Wait for 800 ms before calling `get_or_try_insert_with`. + // Wait for 800 ms before calling `try_get_with`. sleep(Duration::from_millis(800)); - let v: MyResult<_> = cache5.get_or_try_insert_with(KEY, || unreachable!()); + let v: MyResult<_> = cache5.try_get_with(KEY, || unreachable!()); assert_eq!(v.unwrap(), "thread3"); }) }; @@ -1343,7 +1345,7 @@ mod tests { #[test] // https://github.com/moka-rs/moka/issues/43 - fn handle_panic_in_get_or_insert_with() { + fn handle_panic_in_get_with() { use std::{sync::Barrier, thread}; let cache = Cache::new(16); @@ -1352,21 +1354,21 @@ mod tests { let cache_ref = cache.clone(); let barrier_ref = barrier.clone(); thread::spawn(move || { - let _ = cache_ref.get_or_insert_with(1, || { + let _ = cache_ref.get_with(1, || { barrier_ref.wait(); thread::sleep(Duration::from_millis(50)); - panic!("Panic during get_or_try_insert_with"); + panic!("Panic during get_with"); }); }); } barrier.wait(); - assert_eq!(cache.get_or_insert_with(1, || 5), 5); + assert_eq!(cache.get_with(1, || 5), 5); } #[test] // https://github.com/moka-rs/moka/issues/43 - fn handle_panic_in_get_or_try_insert_with() { + fn handle_panic_in_try_get_with() { use std::{sync::Barrier, thread}; let cache = Cache::new(16); @@ -1375,17 +1377,17 @@ mod tests { let cache_ref = cache.clone(); let barrier_ref = barrier.clone(); thread::spawn(move || { - let _ = cache_ref.get_or_try_insert_with(1, || { + let _ = cache_ref.try_get_with(1, || { barrier_ref.wait(); thread::sleep(Duration::from_millis(50)); - panic!("Panic during get_or_try_insert_with"); + panic!("Panic during try_get_with"); }) as Result<_, Arc>; }); } barrier.wait(); assert_eq!( - cache.get_or_try_insert_with(1, || Ok(5)) as Result<_, Arc>, + cache.try_get_with(1, || Ok(5)) as Result<_, Arc>, Ok(5) ); } diff --git a/src/sync/segment.rs b/src/sync/segment.rs index f1c2a9fd..3f03d9d5 100644 --- a/src/sync/segment.rs +++ b/src/sync/segment.rs @@ -1,10 +1,9 @@ use super::{cache::Cache, CacheBuilder, ConcurrentCacheExt, Weigher}; -use crate::PredicateError; +use crate::{Policy, PredicateError}; use std::{ borrow::Borrow, collections::hash_map::RandomState, - error::Error, hash::{BuildHasher, Hash, Hasher}, sync::Arc, time::Duration, @@ -146,6 +145,22 @@ where self.inner.select(hash).get_with_hash(key, hash) } + /// Deprecated, replaced with [`get_with`](#method.get_with) + #[deprecated(since = "0.8.0", note = "Replaced with `get_with`")] + pub fn get_or_insert_with(&self, key: K, init: impl FnOnce() -> V) -> V { + self.get_with(key, init) + } + + /// Deprecated, replaced with [`try_get_with`](#method.try_get_with) + #[deprecated(since = "0.8.0", note = "Replaced with `try_get_with`")] + pub fn get_or_try_insert_with(&self, key: K, init: F) -> Result> + where + F: FnOnce() -> Result, + E: Send + Sync + 'static, + { + self.try_get_with(key, init) + } + /// Ensures the value of the key exists by inserting the result of the init /// closure if not exist, and returns a _clone_ of the value. /// @@ -153,7 +168,7 @@ where /// key even if the method is concurrently called by many threads; only one of /// the calls evaluates its closure, and other calls wait for that closure to /// complete. - pub fn get_or_insert_with(&self, key: K, init: impl FnOnce() -> V) -> V { + pub fn get_with(&self, key: K, init: impl FnOnce() -> V) -> V { let hash = self.inner.hash(&key); let key = Arc::new(key); self.inner @@ -169,10 +184,10 @@ where /// key even if the method is concurrently called by many threads; only one of /// the calls evaluates its closure (as long as these closures return the same /// error type), and other calls wait for that closure to complete. - pub fn get_or_try_insert_with(&self, key: K, init: F) -> Result> + pub fn try_get_with(&self, key: K, init: F) -> Result> where F: FnOnce() -> Result, - E: Error + Send + Sync + 'static, + E: Send + Sync + 'static, { let hash = self.inner.hash(&key); let key = Arc::new(key); @@ -255,24 +270,15 @@ where Ok(()) } - /// Returns the `max_capacity` of this cache. - pub fn max_capacity(&self) -> Option { - self.inner.desired_capacity - } - - /// Returns the `time_to_live` of this cache. - pub fn time_to_live(&self) -> Option { - self.inner.segments[0].time_to_live() - } - - /// Returns the `time_to_idle` of this cache. - pub fn time_to_idle(&self) -> Option { - self.inner.segments[0].time_to_idle() - } - - /// Returns the number of internal segments of this cache. - pub fn num_segments(&self) -> usize { - self.inner.segments.len() + /// Returns a read-only cache policy of this cache. + /// + /// At this time, cache policy cannot be modified after cache creation. + /// A future version may support to modify it. + pub fn policy(&self) -> Policy { + let mut policy = self.inner.segments[0].policy(); + policy.set_max_capacity(self.inner.desired_capacity); + policy.set_num_segments(self.inner.segments.len()); + policy } #[cfg(test)] @@ -721,7 +727,7 @@ mod tests { } #[test] - fn get_or_insert_with() { + fn get_with() { use std::thread::{sleep, spawn}; let cache = SegmentedCache::new(100, 4); @@ -729,14 +735,14 @@ mod tests { // This test will run five threads: // - // Thread1 will be the first thread to call `get_or_insert_with` for a key, so + // Thread1 will be the first thread to call `get_with` for a key, so // its async block will be evaluated and then a &str value "thread1" will be // inserted to the cache. let thread1 = { let cache1 = cache.clone(); spawn(move || { - // Call `get_or_insert_with` immediately. - let v = cache1.get_or_insert_with(KEY, || { + // Call `get_with` immediately. + let v = cache1.get_with(KEY, || { // Wait for 300 ms and return a &str value. sleep(Duration::from_millis(300)); "thread1" @@ -745,20 +751,20 @@ mod tests { }) }; - // Thread2 will be the second thread to call `get_or_insert_with` for the same + // Thread2 will be the second thread to call `get_with` for the same // key, so its async block will not be evaluated. Once thread1's async block // finishes, it will get the value inserted by thread1's async block. let thread2 = { let cache2 = cache.clone(); spawn(move || { - // Wait for 100 ms before calling `get_or_insert_with`. + // Wait for 100 ms before calling `get_with`. sleep(Duration::from_millis(100)); - let v = cache2.get_or_insert_with(KEY, || unreachable!()); + let v = cache2.get_with(KEY, || unreachable!()); assert_eq!(v, "thread1"); }) }; - // Thread3 will be the third thread to call `get_or_insert_with` for the same + // Thread3 will be the third thread to call `get_with` for the same // key. By the time it calls, thread1'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 thread1's @@ -766,9 +772,9 @@ mod tests { let thread3 = { let cache3 = cache.clone(); spawn(move || { - // Wait for 400 ms before calling `get_or_insert_with`. + // Wait for 400 ms before calling `get_with`. sleep(Duration::from_millis(400)); - let v = cache3.get_or_insert_with(KEY, || unreachable!()); + let v = cache3.get_with(KEY, || unreachable!()); assert_eq!(v, "thread1"); }) }; @@ -803,7 +809,7 @@ mod tests { } #[test] - fn get_or_try_insert_with() { + fn try_get_with() { use std::{ sync::Arc, thread::{sleep, spawn}, @@ -820,14 +826,14 @@ mod tests { // This test will run eight async threads: // - // Thread1 will be the first thread to call `get_or_insert_with` for a key, so + // Thread1 will be the first thread 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 thread1 = { let cache1 = cache.clone(); spawn(move || { - // Call `get_or_try_insert_with` immediately. - let v = cache1.get_or_try_insert_with(KEY, || { + // Call `try_get_with` immediately. + let v = cache1.try_get_with(KEY, || { // Wait for 300 ms and return an error. sleep(Duration::from_millis(300)); Err(MyError("thread1 error".into())) @@ -836,21 +842,21 @@ mod tests { }) }; - // Thread2 will be the second thread to call `get_or_insert_with` for the same + // Thread2 will be the second thread to call `get_with` for the same // key, so its async block will not be evaluated. Once thread1's async block // finishes, it will get the same error value returned by thread1's async // block. let thread2 = { let cache2 = cache.clone(); spawn(move || { - // Wait for 100 ms before calling `get_or_try_insert_with`. + // Wait for 100 ms before calling `try_get_with`. sleep(Duration::from_millis(100)); - let v: MyResult<_> = cache2.get_or_try_insert_with(KEY, || unreachable!()); + let v: MyResult<_> = cache2.try_get_with(KEY, || unreachable!()); assert!(v.is_err()); }) }; - // Thread3 will be the third thread to call `get_or_insert_with` for the same + // Thread3 will be the third thread to call `get_with` for the same // key. By the time it calls, thread1'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 @@ -858,9 +864,9 @@ mod tests { let thread3 = { let cache3 = cache.clone(); spawn(move || { - // Wait for 400 ms before calling `get_or_try_insert_with`. + // Wait for 400 ms before calling `try_get_with`. sleep(Duration::from_millis(400)); - let v: MyResult<_> = cache3.get_or_try_insert_with(KEY, || { + let v: MyResult<_> = cache3.try_get_with(KEY, || { // Wait for 300 ms and return an Ok(&str) value. sleep(Duration::from_millis(300)); Ok("thread3") @@ -869,20 +875,20 @@ mod tests { }) }; - // thread4 will be the fourth thread to call `get_or_insert_with` for the same + // thread4 will be the fourth thread to call `get_with` for the same // key. So its async block will not be evaluated. Once thread3's async block // finishes, it will get the same okay &str value. let thread4 = { let cache4 = cache.clone(); spawn(move || { - // Wait for 500 ms before calling `get_or_try_insert_with`. + // Wait for 500 ms before calling `try_get_with`. sleep(Duration::from_millis(500)); - let v: MyResult<_> = cache4.get_or_try_insert_with(KEY, || unreachable!()); + let v: MyResult<_> = cache4.try_get_with(KEY, || unreachable!()); assert_eq!(v.unwrap(), "thread3"); }) }; - // Thread5 will be the fifth thread to call `get_or_insert_with` for the same + // Thread5 will be the fifth thread to call `get_with` for the same // key. So its async block will not be evaluated. By the time it calls, // thread3's async block should have finished already, so its async block will // not be evaluated and will get the value insert by thread3's async block @@ -890,9 +896,9 @@ mod tests { let thread5 = { let cache5 = cache.clone(); spawn(move || { - // Wait for 800 ms before calling `get_or_try_insert_with`. + // Wait for 800 ms before calling `try_get_with`. sleep(Duration::from_millis(800)); - let v: MyResult<_> = cache5.get_or_try_insert_with(KEY, || unreachable!()); + let v: MyResult<_> = cache5.try_get_with(KEY, || unreachable!()); assert_eq!(v.unwrap(), "thread3"); }) }; diff --git a/src/unsync/builder.rs b/src/unsync/builder.rs index 54dc096b..865bb6d2 100644 --- a/src/unsync/builder.rs +++ b/src/unsync/builder.rs @@ -193,10 +193,11 @@ mod tests { async fn build_cache() { // Cache let mut cache = CacheBuilder::new(100).build(); + let policy = cache.policy(); - assert_eq!(cache.max_capacity(), Some(100)); - assert_eq!(cache.time_to_live(), None); - assert_eq!(cache.time_to_idle(), None); + assert_eq!(policy.max_capacity(), Some(100)); + assert_eq!(policy.time_to_live(), None); + assert_eq!(policy.time_to_idle(), None); cache.insert('a', "Alice"); assert_eq!(cache.get(&'a'), Some(&"Alice")); @@ -205,10 +206,11 @@ mod tests { .time_to_live(Duration::from_secs(45 * 60)) .time_to_idle(Duration::from_secs(15 * 60)) .build(); + let policy = cache.policy(); - assert_eq!(cache.max_capacity(), Some(100)); - assert_eq!(cache.time_to_live(), Some(Duration::from_secs(45 * 60))); - assert_eq!(cache.time_to_idle(), Some(Duration::from_secs(15 * 60))); + assert_eq!(policy.max_capacity(), Some(100)); + assert_eq!(policy.time_to_live(), Some(Duration::from_secs(45 * 60))); + assert_eq!(policy.time_to_idle(), Some(Duration::from_secs(15 * 60))); cache.insert('a', "Alice"); assert_eq!(cache.get(&'a'), Some(&"Alice")); diff --git a/src/unsync/cache.rs b/src/unsync/cache.rs index 2baedb69..cfb9878d 100644 --- a/src/unsync/cache.rs +++ b/src/unsync/cache.rs @@ -1,10 +1,13 @@ use super::{deques::Deques, AccessTime, CacheBuilder, KeyDate, KeyHashDate, ValueEntry, Weigher}; -use crate::common::{ - self, - deque::{DeqNode, Deque}, - frequency_sketch::FrequencySketch, - time::{CheckedTimeOps, Clock, Instant}, - CacheRegion, +use crate::{ + common::{ + self, + deque::{DeqNode, Deque}, + frequency_sketch::FrequencySketch, + time::{CheckedTimeOps, Clock, Instant}, + CacheRegion, + }, + Policy, }; use smallvec::SmallVec; @@ -361,19 +364,12 @@ where self.saturating_sub_from_total_weight(invalidated); } - /// Returns the `max_capacity` of this cache. - pub fn max_capacity(&self) -> Option { - self.max_capacity.map(|n| n as usize) - } - - /// Returns the `time_to_live` of this cache. - pub fn time_to_live(&self) -> Option { - self.time_to_live - } - - /// Returns the `time_to_idle` of this cache. - pub fn time_to_idle(&self) -> Option { - self.time_to_idle + /// Returns a read-only cache policy of this cache. + /// + /// At this time, cache policy cannot be modified after cache creation. + /// A future version may support to modify it. + pub fn policy(&self) -> Policy { + Policy::new(self.max_capacity, 1, self.time_to_live, self.time_to_idle) } }