poolcache/
lib.rs

1//! A PoolCache is a hybrid LFU cache and object pool, that allows
2//! for caching behavior with the possibility of reusing object
3//! rather than dropping them from the cache automatically.
4//!
5//! # Examples
6//! ```
7//! use poolcache::PoolCache;
8//!
9//! // Create a new pool cache with a maximum 'heat' of 4.
10//! // Larger maxium heat values make popular values more resistent
11//! // to being reused, but at the cost of increasing the potential
12//! // work required to find a re-usable entry.
13//! let mut cache : PoolCache<u64, Vec<u8>> = PoolCache::new(4);
14//!
15//! // Caches are empty until you populate them..`insert` adds a 
16//! // new value associated with a key.
17//! cache.insert(1, Vec::new());
18//!
19//! // `cache` now contains a single vector, associated with the key
20//! // `1`, which can be retrieved with `get`
21//! {
22//!     let vecref : &Vec<u8> = cache.get(&1).unwrap();
23//! }
24//!
25//! // You can also add values that aren't associated with any key with
26//! // `put`. These newly added values will be used to satisfy `take`
27//! // requests before evicting objects with keys.
28//! cache.put(Vec::new());
29//!
30//! // You can get an owned object from the pool using `take`. This will
31//! // use any free objects (if available), or evict the least `hot`
32//! // key from the cache, and return its value.
33//! let ownedvec : Vec<u8> = cache.take().unwrap();
34//! ```
35//!
36
37use std::cell::Cell;
38use std::cmp;
39use std::collections::{BTreeMap,VecDeque};
40
41struct CacheEntry<Value> {
42    val: Value,
43    heat: Cell<u64>,
44}
45
46impl<Value> CacheEntry<Value> {
47    fn new(val: Value) -> CacheEntry<Value> {
48        CacheEntry{val: val, heat: Cell::new(1)}
49    }
50
51    fn inc(&self, max_heat: u64) -> u64 {
52        self.heat.set(cmp::min(self.heat.get() + 1, max_heat));
53        self.heat.get()
54    }
55
56    fn dec(&self) -> u64 {
57        self.heat.set(cmp::max(self.heat.get() - 1, 0));
58        self.heat.get()
59    }
60}
61
62pub struct PoolCache<Key, Value> {
63    cache: BTreeMap<Key, CacheEntry<Value>>,
64    freelist: VecDeque<Value>,
65    clock: VecDeque<Key>,
66    max_heat: u64,
67}
68
69impl<Key, Value> PoolCache<Key, Value>
70    where Key: PartialOrd + Ord + Clone {
71
72        /// Create a new PoolCache where the maximum heat of a value
73        /// is limited to `max_heat`.
74        pub fn new(max_heat: u64) -> PoolCache<Key, Value> {
75            PoolCache{
76                cache: BTreeMap::new(),
77                freelist: VecDeque::new(),
78                clock: VecDeque::new(),
79                max_heat: max_heat}
80        }
81
82        /// Returns `true` if the given key is present in the cache.
83        pub fn contains_key(&self, key: &Key) -> bool {
84            self.cache.contains_key(key)
85        }
86
87        /// Returns a reference to the value associated with `key`, or `None`
88        /// if the key is not present in the cache.
89        pub fn get(&self, key: &Key) -> Option<&Value> {
90            self.cache.get(key).and_then(|entry| {
91                entry.inc(self.max_heat);
92                Some(&entry.val)
93            })
94        }
95
96        /// Add a new object to the pool, not associated with any
97        /// key. This will become available to any callers of `take`. 
98        pub fn put(&mut self, val: Value) {
99            self.freelist.push_back(val)
100        }
101
102        /// Insert `val` into the map associated with `key`. Any previous
103        /// entry for `key` will be replaced, and the old value will become
104        /// available for new callers of `take`.
105        pub fn insert(&mut self, key: Key, val: Value) {
106            let mut found_entry = false;
107            if let Some(old_entry) = self.cache.remove(&key) {
108                self.freelist.push_back(old_entry.val);
109                found_entry = true;
110            }
111            if !found_entry {
112                self.clock.push_back(key.clone());
113            }
114            self.cache.insert(key, CacheEntry::new(val));
115        }
116
117        /// Take returns an object from the pool, evicting the least-used
118        /// cached key if necessary. Returns `None` only if the PoolCache
119        /// contains no items.
120        pub fn take(&mut self) -> Option<Value> {
121            if let Some(val) = self.freelist.pop_front() {
122                return Some(val);
123            }
124            // cache is empty.
125            if self.clock.is_empty() {
126                return None;
127            }
128            // loop over the elements in `clock`, decrementing heat until
129            // we find an eligible value to evict.
130            loop {
131                let key = self.clock.pop_front().unwrap();
132                let heat = self.cache.get(&key).unwrap().dec();
133                if heat == 0 {
134                    // eligible element.
135                    return Some(self.cache.remove(&key).unwrap().val);
136                }
137                // non-zero heat, keep looping.
138                self.clock.push_back(key);
139            }
140        }
141}
142
143
144#[cfg(test)]
145mod test {
146    #[test]
147    fn basic() {
148        // can't take from an empty cache.
149        let mut cache: super::PoolCache<u64, String> = super::PoolCache::new(5);
150        assert_eq!(None, cache.take());
151
152        // adding an object to the cache not associated with a value,
153        // and returning it via 'take'.
154        cache.put(String::from("foo"));
155        assert_eq!(Some(String::from("foo")), cache.take());
156
157        // Since we only added one value (and then took it), the
158        // cache is empty again.
159        assert_eq!(None, cache.take());
160
161        // Add a keyed value, and retrieve it a few times.
162        cache.insert(1, String::from("bar"));
163        assert_eq!("bar", cache.get(&1).unwrap());
164        assert_eq!("bar", cache.get(&1).unwrap());
165        assert_eq!("bar", cache.get(&1).unwrap());
166
167        // Add a second value, and retrieve it only once.
168        cache.insert(2, String::from("baz"));
169        assert_eq!("baz", cache.get(&2).unwrap());
170
171
172        // taking a value returns 'baz', since it only has one use.
173        assert_eq!(Some(String::from("baz")), cache.take());
174
175        // Now that we've taken it's value, the key '2' is no longer
176        // in the cache.
177        assert_eq!(None, cache.get(&2));
178
179        // '1' is still in the cache
180        assert!(cache.contains_key(&1));
181
182        // Replace it's value (currently 'bar') with a new value.
183        cache.insert(1, String::from("newbar"));
184        assert_eq!("newbar", cache.get(&1).unwrap());
185
186        // The old value ('bar') is moved to the freelist, and is
187        // returned to the next caller of `take`
188        assert_eq!(Some(String::from("bar")), cache.take());
189
190        // A final `take` removes the last value in the pool 
191        // (currently keyed to '1')
192        assert_eq!(Some(String::from("newbar")), cache.take());
193
194        // leaving the cache empty.
195        assert_eq!(None, cache.take());
196    }
197}