oxistore_cache/
bounded.rs1use std::collections::VecDeque;
9
10use crate::Cache;
11
12pub struct BoundedCache<C> {
22 inner: C,
23 max_bytes: usize,
24 current_bytes: usize,
25 order: VecDeque<Vec<u8>>,
27}
28
29impl<C> BoundedCache<C>
30where
31 C: Cache<Vec<u8>, Vec<u8>>,
32{
33 pub fn new(inner: C, max_bytes: usize) -> Self {
37 BoundedCache {
38 inner,
39 max_bytes,
40 current_bytes: 0,
41 order: VecDeque::new(),
42 }
43 }
44
45 #[must_use]
47 pub fn current_bytes(&self) -> usize {
48 self.current_bytes
49 }
50
51 #[must_use]
53 pub fn max_bytes(&self) -> usize {
54 self.max_bytes
55 }
56
57 fn evict_until_fits(&mut self, needed: usize) {
60 while self.current_bytes + needed > self.max_bytes {
61 let oldest = match self.order.pop_front() {
62 Some(k) => k,
63 None => break,
64 };
65 if let Some(old_val) = self.inner.remove(&oldest) {
66 let freed = oldest.len() + old_val.len();
67 self.current_bytes = self.current_bytes.saturating_sub(freed);
68 }
69 }
70 }
71}
72
73impl<C> Cache<Vec<u8>, Vec<u8>> for BoundedCache<C>
74where
75 C: Cache<Vec<u8>, Vec<u8>>,
76{
77 fn get(&mut self, key: &Vec<u8>) -> Option<&Vec<u8>> {
78 self.inner.get(key)
79 }
80
81 fn put(&mut self, key: Vec<u8>, value: Vec<u8>) -> Option<Vec<u8>> {
82 let entry_size = key.len() + value.len();
83
84 self.evict_until_fits(entry_size);
86
87 if let Some(existing) = self.inner.peek(&key) {
89 let old_size = key.len() + existing.len();
90 self.current_bytes = self.current_bytes.saturating_sub(old_size);
91 self.order.retain(|k| k != &key);
93 }
94
95 self.current_bytes += entry_size;
96 self.order.push_back(key.clone());
97 self.inner.put(key, value)
98 }
99
100 fn put_with_ttl(
101 &mut self,
102 key: Vec<u8>,
103 value: Vec<u8>,
104 ttl: std::time::Duration,
105 ) -> Option<Vec<u8>> {
106 let entry_size = key.len() + value.len();
107 self.evict_until_fits(entry_size);
108
109 if let Some(existing) = self.inner.peek(&key) {
110 let old_size = key.len() + existing.len();
111 self.current_bytes = self.current_bytes.saturating_sub(old_size);
112 self.order.retain(|k| k != &key);
113 }
114
115 self.current_bytes += entry_size;
116 self.order.push_back(key.clone());
117 self.inner.put_with_ttl(key, value, ttl)
118 }
119
120 fn len(&self) -> usize {
121 self.inner.len()
122 }
123
124 fn cap(&self) -> usize {
125 self.inner.cap()
126 }
127
128 fn remove(&mut self, key: &Vec<u8>) -> Option<Vec<u8>> {
129 if let Some(val) = self.inner.remove(key) {
130 let freed = key.len() + val.len();
131 self.current_bytes = self.current_bytes.saturating_sub(freed);
132 self.order.retain(|k| k != key);
133 Some(val)
134 } else {
135 None
136 }
137 }
138
139 fn clear(&mut self) {
140 self.inner.clear();
141 self.current_bytes = 0;
142 self.order.clear();
143 }
144
145 fn peek(&self, key: &Vec<u8>) -> Option<&Vec<u8>> {
146 self.inner.peek(key)
147 }
148
149 fn contains_key(&self, key: &Vec<u8>) -> bool {
150 self.inner.contains_key(key)
151 }
152
153 fn resize(&mut self, new_cap: usize) {
154 self.inner.resize(new_cap);
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161 use crate::LruCache;
162
163 fn make_bounded(max_bytes: usize, cap: usize) -> BoundedCache<LruCache<Vec<u8>, Vec<u8>>> {
164 BoundedCache::new(LruCache::new(cap), max_bytes)
165 }
166
167 #[test]
168 fn bounded_under_budget() {
169 let mut cache = make_bounded(100, 10);
170 cache.put(b"key1".to_vec(), b"val1".to_vec());
171 cache.put(b"key2".to_vec(), b"val2".to_vec());
172 assert!(cache.current_bytes() <= 100);
173 assert_eq!(cache.get(&b"key1".to_vec()), Some(&b"val1".to_vec()));
174 }
175
176 #[test]
177 fn bounded_evicts_when_over_budget() {
178 let mut cache = make_bounded(16, 100);
180 cache.put(b"key1".to_vec(), b"val1".to_vec()); cache.put(b"key2".to_vec(), b"val2".to_vec()); assert_eq!(cache.current_bytes(), 16);
183
184 cache.put(b"key3".to_vec(), b"val3".to_vec());
186 assert!(cache.current_bytes() <= 16);
187 assert!(cache.get(&b"key1".to_vec()).is_none());
188 }
189
190 #[test]
191 fn bounded_remove_updates_bytes() {
192 let mut cache = make_bounded(100, 10);
193 cache.put(b"hello".to_vec(), b"world".to_vec());
194 let before = cache.current_bytes();
195 cache.remove(&b"hello".to_vec());
196 assert_eq!(cache.current_bytes(), before - 10); }
198
199 #[test]
200 fn bounded_clear_resets_bytes() {
201 let mut cache = make_bounded(100, 10);
202 cache.put(b"a".to_vec(), b"b".to_vec());
203 cache.clear();
204 assert_eq!(cache.current_bytes(), 0);
205 }
206
207 #[test]
208 fn bounded_update_existing_key() {
209 let mut cache = make_bounded(100, 10);
210 cache.put(b"key".to_vec(), b"short".to_vec()); cache.put(b"key".to_vec(), b"longer_value".to_vec()); assert!(cache.current_bytes() <= 100);
213 assert_eq!(cache.get(&b"key".to_vec()), Some(&b"longer_value".to_vec()));
214 }
215}