diff --git a/Cargo.toml b/Cargo.toml index c300882..4ec25e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ttl_cache" -version = "0.2.1" +version = "0.3.0" authors = ["Stu Small "] description = "A cache that will expire values after a TTL" repository = "https://github.com/stusmall/ttl_cache" @@ -9,4 +9,4 @@ keywords = ["cache","ttl","expire"] license = "MIT/Apache-2.0" [dependencies] -linked-hash-map = "0.3" +linked-hash-map = "0.4" diff --git a/src/lib.rs b/src/lib.rs index 7f7764a..4dc7da2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ -//! This crate provides a time sensitive key-value FIFO cache. When the cache is created it is -//! given a TTL. Any value that are in the cache for longer than this duration are considered -//! invalid and will not be returned. +//! This crate provides a time sensitive key-value cache. When an item is inserted it is +//! given a TTL. Any value that are in the cache after their duration are considered invalid +//! and will not be returned on lookups. extern crate linked_hash_map; @@ -36,12 +36,10 @@ impl Entry { pub struct TtlCache { map: LinkedHashMap, S>, max_size: usize, - duration: Duration, } impl TtlCache { - /// Creates an empty cache that can hold at most `capacity` items and will expire them - /// after `duration` + /// Creates an empty cache that can hold at most `capacity` items. /// /// # Examples /// @@ -49,13 +47,12 @@ impl TtlCache { /// use std::time::Duration; /// use ttl_cache::TtlCache; /// - /// let mut cache: TtlCache = TtlCache::new(Duration::from_secs(30), 10); + /// let mut cache: TtlCache = TtlCache::new(10); /// ``` - pub fn new(duration: Duration, capacity: usize) -> Self { + pub fn new(capacity: usize) -> Self { TtlCache { map: LinkedHashMap::new(), max_size: capacity, - duration: duration, } } } @@ -63,11 +60,10 @@ impl TtlCache { impl TtlCache { /// Creates an empty cache that can hold at most `capacity` items /// that expire after `duration` with the given hash builder. - pub fn with_hasher(duration: Duration, capacity: usize, hash_builder: S) -> Self { + pub fn with_hasher(capacity: usize, hash_builder: S) -> Self { TtlCache { map: LinkedHashMap::with_hasher(hash_builder), max_size: capacity, - duration: duration, } } @@ -78,8 +74,8 @@ impl TtlCache { /// use std::time::Duration; /// use ttl_cache::TtlCache; /// - /// let mut cache = TtlCache::new(Duration::from_secs(30), 10); - /// cache.insert(1,"a"); + /// let mut cache = TtlCache::new(10); + /// cache.insert(1,"a", Duration::from_secs(30)); /// assert_eq!(cache.contains_key(&1), true); /// ``` pub fn contains_key(&mut self, key: &Q) -> bool @@ -91,32 +87,6 @@ impl TtlCache { } - - /// Inserts a key-value pair into the cache. If the key already existed and hasn't expired, - /// the old value is returned. - /// - /// # Examples - /// - /// ``` - /// use std::time::Duration; - /// use ttl_cache::TtlCache; - /// - /// let mut cache = TtlCache::new(Duration::from_secs(30), 2); - /// - /// cache.insert(1, "a"); - /// cache.insert(2, "b"); - /// assert_eq!(cache.get_mut(&1), Some(&mut "a")); - /// assert_eq!(cache.get_mut(&2), Some(&mut "b")); - /// ``` - pub fn insert(&mut self, k: K, v: V) -> Option { - let to_insert = Entry::new(v, self.duration); - let old_val = self.map.insert(k, to_insert); - if self.len() > self.capacity() { - self.remove_oldest(); - } - old_val.and_then(|x| if x.is_expired() { None } else { Some(x.value) }) - } - /// Inserts a key-value pair into the cache with an individual ttl for the key. If the key already existed and hasn't expired, /// the old value is returned. /// @@ -126,14 +96,14 @@ impl TtlCache { /// use std::time::Duration; /// use ttl_cache::TtlCache; /// - /// let mut cache = TtlCache::new(Duration::from_secs(30), 2); + /// let mut cache = TtlCache::new(2); /// - /// cache.insert_with_ttl(1, "a", Duration::from_secs(20)); - /// cache.insert_with_ttl(2, "b", Duration::from_secs(60)); + /// cache.insert(1, "a", Duration::from_secs(20)); + /// cache.insert(2, "b", Duration::from_secs(60)); /// assert_eq!(cache.get_mut(&1), Some(&mut "a")); /// assert_eq!(cache.get_mut(&2), Some(&mut "b")); /// ``` - pub fn insert_with_ttl(&mut self, k: K, v: V, ttl: Duration) -> Option { + pub fn insert(&mut self, k: K, v: V, ttl: Duration) -> Option { let to_insert = Entry::new(v, ttl); let old_val = self.map.insert(k, to_insert); if self.len() > self.capacity() { @@ -151,12 +121,13 @@ impl TtlCache { /// use std::time::Duration; /// use ttl_cache::TtlCache; /// - /// let mut cache = TtlCache::new(Duration::from_secs(30), 2); + /// let mut cache = TtlCache::new(2); + /// let duration = Duration::from_secs(30); /// - /// cache.insert(1, "a"); - /// cache.insert(2, "b"); - /// cache.insert(2, "c"); - /// cache.insert(3, "d"); + /// cache.insert(1, "a", duration); + /// cache.insert(2, "b", duration); + /// cache.insert(2, "c", duration); + /// cache.insert(3, "d", duration); /// /// assert_eq!(cache.get_mut(&1), None); /// assert_eq!(cache.get_mut(&2), Some(&mut "c")); @@ -165,12 +136,10 @@ impl TtlCache { where K: Borrow, Q: Hash + Eq { - self.map.get_mut(k).and_then(|mut x| { - if x.is_expired() { - None - } else { - Some(&mut x.value) - } + self.map.get_mut(k).and_then(|mut x| if x.is_expired() { + None + } else { + Some(&mut x.value) }) } @@ -183,9 +152,9 @@ impl TtlCache { /// use std::time::Duration; /// use ttl_cache::TtlCache; /// - /// let mut cache = TtlCache::new(Duration::from_secs(30), 2); + /// let mut cache = TtlCache::new(2); /// - /// cache.insert(2, "a"); + /// cache.insert(2, "a", Duration::from_secs(30)); /// /// assert_eq!(cache.remove(&1), None); /// assert_eq!(cache.remove(&2), Some("a")); @@ -207,7 +176,7 @@ impl TtlCache { /// use std::time::Duration; /// use ttl_cache::TtlCache; /// - /// let mut cache: TtlCache = TtlCache::new(Duration::from_secs(30), 2); + /// let mut cache: TtlCache = TtlCache::new(2); /// assert_eq!(cache.capacity(), 2); /// ``` pub fn capacity(&self) -> usize { @@ -224,19 +193,20 @@ impl TtlCache { /// use std::time::Duration; /// use ttl_cache::TtlCache; /// - /// let mut cache = TtlCache::new(Duration::from_secs(30), 2); + /// let mut cache = TtlCache::new(2); + /// let duration = Duration::from_secs(30); /// - /// cache.insert(1, "a"); - /// cache.insert(2, "b"); - /// cache.insert(3, "c"); + /// cache.insert(1, "a", duration); + /// cache.insert(2, "b", duration); + /// cache.insert(3, "c", duration); /// /// assert_eq!(cache.get_mut(&1), None); /// assert_eq!(cache.get_mut(&2), Some(&mut "b")); /// assert_eq!(cache.get_mut(&3), Some(&mut "c")); /// /// cache.set_capacity(3); - /// cache.insert(1, "a"); - /// cache.insert(2, "b"); + /// cache.insert(1, "a", duration); + /// cache.insert(2, "b", duration); /// /// assert_eq!(cache.get_mut(&1), Some(&mut "a")); /// assert_eq!(cache.get_mut(&2), Some(&mut "b")); @@ -268,11 +238,12 @@ impl TtlCache { /// use std::time::Duration; /// use ttl_cache::TtlCache; /// - /// let mut cache = TtlCache::new(Duration::from_secs(30), 2); + /// let mut cache = TtlCache::new(2); + /// let duration = Duration::from_secs(30); /// - /// cache.insert(1, 10); - /// cache.insert(2, 20); - /// cache.insert(3, 30); + /// cache.insert(1, 10, duration); + /// cache.insert(2, 20, duration); + /// cache.insert(3, 30, duration); /// /// let kvs: Vec<_> = cache.iter().collect(); /// assert_eq!(kvs, [(&2, &20), (&3, &30)]); @@ -292,11 +263,12 @@ impl TtlCache { /// use std::time::Duration; /// use ttl_cache::TtlCache; /// - /// let mut cache = TtlCache::new(Duration::from_secs(30), 2); + /// let mut cache = TtlCache::new(2); + /// let duration = Duration::from_secs(30); /// - /// cache.insert(1, 10); - /// cache.insert(2, 20); - /// cache.insert(3, 30); + /// cache.insert(1, 10, duration); + /// cache.insert(2, 20, duration); + /// cache.insert(3, 30, duration); /// /// let mut n = 2; /// @@ -325,11 +297,9 @@ impl TtlCache { } fn remove_expired(&mut self) { - let should_pop_head = |map: &LinkedHashMap, S>| { - match map.front() { - Some(entry) => entry.1.is_expired(), - None => false, - } + let should_pop_head = |map: &LinkedHashMap, S>| match map.front() { + Some(entry) => entry.1.is_expired(), + None => false, }; while should_pop_head(&self.map) { self.map.pop_front(); @@ -341,14 +311,6 @@ impl TtlCache { } } -impl Extend<(K, V)> for TtlCache { - fn extend>(&mut self, iter: I) { - for (k, v) in iter { - self.insert(k, v); - } - } -} - pub struct Iter<'a, K: 'a, V: 'a>(linked_hash_map::Iter<'a, K, Entry>); impl<'a, K, V> Clone for Iter<'a, K, V> { diff --git a/tests/test.rs b/tests/test.rs index 1d98196..d2e5054 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -6,57 +6,46 @@ use std::thread::sleep; #[test] fn test_put_and_get() { - let mut cache = TtlCache::new(Duration::from_secs(60 * 60), 2); - cache.insert(1, 10); - cache.insert(2, 20); + let duration = Duration::from_secs(60 * 60); + let mut cache = TtlCache::new(2); + cache.insert(1, 10, duration); + cache.insert(2, 20, duration); assert_eq!(cache.get_mut(&1), Some(&mut 10)); assert_eq!(cache.get_mut(&2), Some(&mut 20)); } #[test] fn test_put_update() { - let mut cache = TtlCache::new(Duration::from_secs(60 * 60), 1); - cache.insert("1", 10); - cache.insert("1", 19); + let duration = Duration::from_secs(60 * 60); + let mut cache = TtlCache::new(1); + cache.insert("1", 10, duration); + cache.insert("1", 19, duration); assert_eq!(cache.get_mut("1"), Some(&mut 19)); } #[test] fn test_contains_key() { - let mut cache = TtlCache::new(Duration::from_secs(60 * 60), 1); - cache.insert("1", 10); + let duration = Duration::from_secs(60 * 60); + let mut cache = TtlCache::new(1); + cache.insert("1", 10, duration); assert_eq!(cache.contains_key("1"), true); } #[test] fn test_expire_value() { - let mut cache = TtlCache::new(Duration::from_millis(1), 1); - cache.insert("1", 10); - sleep(Duration::from_millis(10)); - assert_eq!(cache.contains_key("1"), false); -} - -#[test] -fn test_individual_ttl_value() { - let mut cache = TtlCache::new(Duration::from_millis(1), 1); - cache.insert_with_ttl("1", 99, Duration::from_millis(15)); - sleep(Duration::from_millis(10)); - assert_eq!(cache.contains_key("1"), true); -} - -#[test] -fn test_individual_ttl_expired() { - let mut cache = TtlCache::new(Duration::from_millis(20), 1); - cache.insert_with_ttl("1", 99, Duration::from_millis(5)); + let duration = Duration::from_millis(1); + let mut cache = TtlCache::new(1); + cache.insert("1", 10, duration); sleep(Duration::from_millis(10)); assert_eq!(cache.contains_key("1"), false); } #[test] fn test_pop() { - let mut cache = TtlCache::new(Duration::from_secs(60 * 60), 2); - cache.insert(1, 10); - cache.insert(2, 20); + let duration = Duration::from_secs(60 * 60); + let mut cache = TtlCache::new(2); + cache.insert(1, 10, duration); + cache.insert(2, 20, duration); let opt1 = cache.remove(&1); assert!(opt1.is_some()); assert_eq!(opt1.unwrap(), 10); @@ -65,10 +54,11 @@ fn test_pop() { #[test] fn test_change_capacity() { - let mut cache = TtlCache::new(Duration::from_secs(60 * 60), 2); + let duration = Duration::from_secs(60 * 60); + let mut cache = TtlCache::new(2); assert_eq!(cache.capacity(), 2); - cache.insert(1, 10); - cache.insert(2, 20); + cache.insert(1, 10, duration); + cache.insert(2, 20, duration); cache.set_capacity(1); assert!(cache.get_mut(&1).is_none()); assert_eq!(cache.capacity(), 1); @@ -76,19 +66,20 @@ fn test_change_capacity() { #[test] fn test_remove() { - let mut cache = TtlCache::new(Duration::from_secs(60 * 60), 3); - cache.insert(1, 10); - cache.insert(2, 20); - cache.insert(3, 30); - cache.insert(4, 40); - cache.insert(5, 50); + let duration = Duration::from_secs(60 * 60); + let mut cache = TtlCache::new(3); + cache.insert(1, 10, duration); + cache.insert(2, 20, duration); + cache.insert(3, 30, duration); + cache.insert(4, 40, duration); + cache.insert(5, 50, duration); cache.remove(&3); cache.remove(&4); assert!(cache.get_mut(&3).is_none()); assert!(cache.get_mut(&4).is_none()); - cache.insert(6, 60); - cache.insert(7, 70); - cache.insert(8, 80); + cache.insert(6, 60, duration); + cache.insert(7, 70, duration); + cache.insert(8, 80, duration); assert!(cache.get_mut(&5).is_none()); assert_eq!(cache.get_mut(&6), Some(&mut 60)); assert_eq!(cache.get_mut(&7), Some(&mut 70)); @@ -97,9 +88,10 @@ fn test_remove() { #[test] fn test_clear() { - let mut cache = TtlCache::new(Duration::from_secs(60 * 60), 2); - cache.insert(1, 10); - cache.insert(2, 20); + let duration = Duration::from_secs(60 * 60); + let mut cache = TtlCache::new(2); + cache.insert(1, 10, duration); + cache.insert(2, 20, duration); cache.clear(); assert!(cache.get_mut(&1).is_none()); assert!(cache.get_mut(&2).is_none()); @@ -107,12 +99,13 @@ fn test_clear() { #[test] fn test_iter() { - let mut cache = TtlCache::new(Duration::from_secs(60 * 60), 3); - cache.insert(1, 10); - cache.insert(2, 20); - cache.insert(3, 30); - cache.insert(4, 40); - cache.insert(5, 50); + let duration = Duration::from_secs(60 * 60); + let mut cache = TtlCache::new(3); + cache.insert(1, 10, duration); + cache.insert(2, 20, duration); + cache.insert(3, 30, duration); + cache.insert(4, 40, duration); + cache.insert(5, 50, duration); assert_eq!(cache.iter().collect::>(), [(&3, &30), (&4, &40), (&5, &50)]); assert_eq!(cache.iter_mut().collect::>(), @@ -126,11 +119,12 @@ fn test_iter() { #[test] fn test_iter_w_expired() { - let mut cache = TtlCache::new(Duration::from_millis(100), 3); - cache.insert(1, 10); + let duration = Duration::from_millis(100); + let mut cache = TtlCache::new(3); + cache.insert(1, 10, duration); sleep(Duration::from_millis(200)); - cache.insert(2, 20); - cache.insert(3, 30); + cache.insert(2, 20, duration); + cache.insert(3, 30, duration); assert_eq!(cache.iter().collect::>(), [(&2, &20), (&3, &30)]); assert_eq!(cache.iter_mut().collect::>(), [(&2, &mut 20), (&3, &mut 30)]); @@ -139,3 +133,14 @@ fn test_iter_w_expired() { assert_eq!(cache.iter_mut().rev().collect::>(), [(&3, &mut 30), (&2, &mut 20)]); } + +#[test] +fn test() { + let mut cache = TtlCache::new(3); + cache.insert(1, 10, Duration::from_millis(300)); + cache.insert(2, 20, Duration::from_millis(10)); + cache.insert(3, 30, Duration::from_millis(300)); + sleep(Duration::from_millis(20)); + assert_eq!(cache.iter().collect::>(), [(&1, &10), (&3, &30)]); + +}