diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f7e8698..c3c548e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Moka Cache — Change Log +## Version 0.11.3 + +### Fixed + +- Fixed a bug in `sync::Cache` and `sync::SegmentedCache` where memory usage kept + increasing when the eviction listener was set with the `Immediate` delivery mode. + ([#295][gh-pull-0295]) + + ## Version 0.11.2 Bumped the minimum supported Rust version (MSRV) to 1.65 (Nov 3, 2022). @@ -676,6 +685,7 @@ The minimum supported Rust version (MSRV) is now 1.51.0 (Mar 25, 2021). [gh-issue-0034]: https://github.com/moka-rs/moka/issues/34/ [gh-issue-0031]: https://github.com/moka-rs/moka/issues/31/ +[gh-pull-0295]: https://github.com/moka-rs/moka/pull/295/ [gh-pull-0277]: https://github.com/moka-rs/moka/pull/277/ [gh-pull-0275]: https://github.com/moka-rs/moka/pull/275/ [gh-pull-0272]: https://github.com/moka-rs/moka/pull/272/ diff --git a/Cargo.toml b/Cargo.toml index e65650af..cc4617ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "moka" -version = "0.11.2" +version = "0.11.3" edition = "2018" # Rust 1.65 was released on Nov 3, 2022. rust-version = "1.65" diff --git a/README.md b/README.md index c8cf8c7b..bd4bd143 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ algorithm to determine which entries to evict when the capacity is exceeded. [release-badge]: https://img.shields.io/crates/v/moka.svg [docs-badge]: https://docs.rs/moka/badge.svg [deps-rs-badge]: https://deps.rs/repo/github/moka-rs/moka/status.svg -[coveralls-badge]: https://coveralls.io/repos/github/moka-rs/moka/badge.svg?branch=master +[coveralls-badge]: https://coveralls.io/repos/github/moka-rs/moka/badge.svg?branch=main [license-badge]: https://img.shields.io/crates/l/moka.svg [fossa-badge]: https://app.fossa.com/api/projects/git%2Bgithub.com%2Fmoka-rs%2Fmoka.svg?type=shield @@ -29,7 +29,7 @@ algorithm to determine which entries to evict when the capacity is exceeded. [crate]: https://crates.io/crates/moka [docs]: https://docs.rs/moka [deps-rs]: https://deps.rs/repo/github/moka-rs/moka -[coveralls]: https://coveralls.io/github/moka-rs/moka?branch=master +[coveralls]: https://coveralls.io/github/moka-rs/moka?branch=main [fossa]: https://app.fossa.com/projects/git%2Bgithub.com%2Fmoka-rs%2Fmoka?ref=badge_shield [caffeine-git]: https://github.com/ben-manes/caffeine @@ -112,7 +112,7 @@ routers. Here are some highlights: ## Change Log -- [CHANGELOG.md](https://github.com/moka-rs/moka/blob/master/CHANGELOG.md) +- [CHANGELOG.md](https://github.com/moka-rs/moka/blob/main/CHANGELOG.md) The `unsync::Cache` and `dash::Cache` have been moved to a separate crate called [Mini Moka][mini-moka-crate]: diff --git a/src/future/cache.rs b/src/future/cache.rs index d5206e78..4d783abb 100644 --- a/src/future/cache.rs +++ b/src/future/cache.rs @@ -2009,6 +2009,10 @@ where fn set_expiration_clock(&self, clock: Option) { self.base.set_expiration_clock(clock); } + + fn key_locks_map_is_empty(&self) -> bool { + self.base.key_locks_map_is_empty() + } } pub struct BlockingOp<'a, K, V, S>(&'a Cache); @@ -2164,6 +2168,7 @@ mod tests { assert!(!cache.contains_key(&"d")); verify_notification_vec(&cache, actual, &expected); + assert!(cache.key_locks_map_is_empty()); } #[test] @@ -2353,6 +2358,7 @@ mod tests { assert_eq!(cache.weighted_size(), 25); verify_notification_vec(&cache, actual, &expected); + assert!(cache.key_locks_map_is_empty()); } #[tokio::test] diff --git a/src/sync/cache.rs b/src/sync/cache.rs index d9348867..23a9d002 100644 --- a/src/sync/cache.rs +++ b/src/sync/cache.rs @@ -2054,6 +2054,10 @@ where pub(crate) fn set_expiration_clock(&self, clock: Option) { self.base.set_expiration_clock(clock); } + + pub(crate) fn key_locks_map_is_empty(&self) -> bool { + self.base.key_locks_map_is_empty() + } } // To see the debug prints, run test as `cargo test -- --nocapture` @@ -2186,6 +2190,7 @@ mod tests { assert!(!cache.contains_key(&"d")); verify_notification_vec(&cache, actual, &expected, delivery_mode); + assert_with_mode!(cache.key_locks_map_is_empty(), delivery_mode); } } @@ -2316,6 +2321,7 @@ mod tests { assert_eq_with_mode!(cache.weighted_size(), 25, delivery_mode); verify_notification_vec(&cache, actual, &expected, delivery_mode); + assert_with_mode!(cache.key_locks_map_is_empty(), delivery_mode); } } @@ -4339,6 +4345,8 @@ mod tests { assert_eq!(a[0], (Arc::new("alice"), "a3", RemovalCause::Expired)); a.clear(); } + + assert!(cache.key_locks_map_is_empty()); } // This test ensures the key-level lock for the immediate notification @@ -4469,6 +4477,8 @@ mod tests { for (i, (actual, expected)) in actual.iter().zip(&expected).enumerate() { assert_eq!(actual, expected, "expected[{}]", i); } + + assert!(cache.key_locks_map_is_empty()); } // NOTE: To enable the panic logging, run the following command: diff --git a/src/sync/segment.rs b/src/sync/segment.rs index 61f07af3..c1728ad3 100644 --- a/src/sync/segment.rs +++ b/src/sync/segment.rs @@ -687,6 +687,13 @@ where exp_clock } + + fn key_locks_map_is_empty(&self) -> bool { + self.inner + .segments + .iter() + .all(|seg| seg.key_locks_map_is_empty()) + } } // For unit tests. @@ -916,6 +923,7 @@ mod tests { assert!(!cache.contains_key(&"d")); verify_notification_vec(&cache, actual, &expected, delivery_mode); + assert_with_mode!(cache.key_locks_map_is_empty(), delivery_mode); } } @@ -1065,6 +1073,7 @@ mod tests { assert_eq_with_mode!(cache.weighted_size(), 25, delivery_mode); verify_notification_vec(&cache, actual, &expected, delivery_mode); + assert_with_mode!(cache.key_locks_map_is_empty(), delivery_mode); } } diff --git a/src/sync_base/base_cache.rs b/src/sync_base/base_cache.rs index cb6de3f0..7dd68220 100644 --- a/src/sync_base/base_cache.rs +++ b/src/sync_base/base_cache.rs @@ -785,6 +785,10 @@ where pub(crate) fn set_expiration_clock(&self, clock: Option) { self.inner.set_expiration_clock(clock); } + + pub(crate) fn key_locks_map_is_empty(&self) -> bool { + self.inner.key_locks_map_is_empty() + } } struct EvictionState<'a, K, V> { @@ -2415,6 +2419,14 @@ where *exp_clock = None; } } + + fn key_locks_map_is_empty(&self) -> bool { + self.key_locks + .as_ref() + .map(|m| m.is_empty()) + // If key_locks is None, consider it is empty. + .unwrap_or(true) + } } // diff --git a/src/sync_base/key_lock.rs b/src/sync_base/key_lock.rs index 7253273a..abc96873 100644 --- a/src/sync_base/key_lock.rs +++ b/src/sync_base/key_lock.rs @@ -30,11 +30,11 @@ where S: BuildHasher, { fn drop(&mut self) { - if TrioArc::count(&self.lock) <= 1 { + if TrioArc::count(&self.lock) <= 2 { self.map.remove_if( self.hash, |k| k == &self.key, - |_k, v| TrioArc::count(v) <= 1, + |_k, v| TrioArc::count(v) <= 2, ); } } @@ -86,3 +86,10 @@ where } } } + +#[cfg(test)] +impl KeyLockMap { + pub(crate) fn is_empty(&self) -> bool { + self.locks.len() == 0 + } +}