reifydb_runtime/cache/sync/
mod.rs1use std::hash::Hash;
5
6use cfg_if::cfg_if;
7use reifydb_value::{byte_size::ByteSize, count::Count};
8
9#[cfg(not(reifydb_single_threaded))]
10pub(crate) mod host;
11#[cfg(reifydb_single_threaded)]
12pub(crate) mod wasm;
13
14cfg_if! {
15 if #[cfg(not(reifydb_single_threaded))] {
16 type LruImpl<K, V> = host::HostLru<K, V>;
17 } else {
18 type LruImpl<K, V> = wasm::WasmLru<K, V>;
19 }
20}
21
22#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
23pub struct CacheFootprint {
24 pub heap: usize,
25 pub payload: usize,
26}
27
28pub type FootprintFn<K, V> = fn(&K, &V) -> CacheFootprint;
29
30#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
31pub struct CacheMemory {
32 pub entries: Count,
33 pub resident: ByteSize,
34 pub payload: ByteSize,
35}
36
37pub struct SyncLru<K, V>
38where
39 K: Hash + Eq + Clone + Send + Sync + 'static,
40 V: Clone + Send + Sync + 'static,
41{
42 inner: LruImpl<K, V>,
43}
44
45impl<K, V> SyncLru<K, V>
46where
47 K: Hash + Eq + Clone + Send + Sync + 'static,
48 V: Clone + Send + Sync + 'static,
49{
50 pub fn new(capacity: usize) -> Self {
51 assert!(capacity > 0, "LRU cache capacity must be greater than 0");
52 Self {
53 inner: LruImpl::new(capacity),
54 }
55 }
56
57 pub fn measured(capacity: usize, footprint: FootprintFn<K, V>) -> Self {
58 assert!(capacity > 0, "LRU cache capacity must be greater than 0");
59 Self {
60 inner: LruImpl::measured(capacity, footprint),
61 }
62 }
63
64 pub fn memory_usage(&self) -> Option<CacheMemory> {
65 self.inner.memory_usage()
66 }
67
68 pub fn get(&self, key: &K) -> Option<V> {
69 self.inner.get(key)
70 }
71
72 pub fn put(&self, key: K, value: V) -> Option<V> {
73 self.inner.put(key, value)
74 }
75
76 pub fn remove(&self, key: &K) -> Option<V> {
77 self.inner.remove(key)
78 }
79
80 pub fn contains_key(&self, key: &K) -> bool {
81 self.inner.contains_key(key)
82 }
83
84 pub fn clear(&self) {
85 self.inner.clear();
86 }
87
88 pub fn len(&self) -> usize {
89 self.inner.len()
90 }
91
92 pub fn is_empty(&self) -> bool {
93 self.len() == 0
94 }
95
96 pub fn capacity(&self) -> usize {
97 self.inner.capacity()
98 }
99
100 pub fn run_pending_tasks(&self) {
101 self.inner.run_pending_tasks();
102 }
103}
104
105#[cfg(all(test, not(reifydb_single_threaded)))]
106mod tests {
107 use std::{mem::size_of, sync::Arc};
108
109 use reifydb_value::{byte_size::ByteSize, count::Count};
110
111 use super::{CacheFootprint, SyncLru};
112
113 fn footprint(_key: &u64, value: &String) -> CacheFootprint {
114 CacheFootprint {
116 heap: value.capacity(),
117 payload: size_of::<u64>() + value.len(),
118 }
119 }
120
121 #[test]
122 fn test_basic_operations() {
123 let cache = SyncLru::new(2);
124
125 assert_eq!(cache.put(1, "a"), None);
126 assert_eq!(cache.put(2, "b"), None);
127 assert_eq!(cache.get(&1), Some("a"));
128 assert_eq!(cache.get(&2), Some("b"));
129 cache.run_pending_tasks();
130 assert_eq!(cache.len(), 2);
131 }
132
133 #[test]
134 fn test_eviction() {
135 let cache = SyncLru::new(2);
136
137 cache.put(1, "a");
138 cache.put(2, "b");
139 let evicted = cache.put(3, "c");
140 cache.run_pending_tasks();
141
142 assert_eq!(evicted, None);
143 assert_eq!(cache.get(&1), None);
144 assert_eq!(cache.get(&2), Some("b"));
145 assert_eq!(cache.get(&3), Some("c"));
146 }
147
148 #[test]
149 fn test_lru_order() {
150 let cache = SyncLru::new(2);
151
152 cache.put(1, "a");
153 cache.put(2, "b");
154 cache.run_pending_tasks();
155 cache.get(&1);
158 cache.run_pending_tasks();
159 cache.put(3, "c");
160 cache.run_pending_tasks();
161
162 assert_eq!(cache.get(&1), Some("a"));
163 assert_eq!(cache.get(&2), None);
164 assert_eq!(cache.get(&3), Some("c"));
165 }
166
167 #[test]
168 fn test_update_existing() {
169 let cache = SyncLru::new(2);
170
171 cache.put(1, "a");
172 let old = cache.put(1, "b");
173
174 assert_eq!(old, Some("a"));
175 assert_eq!(cache.get(&1), Some("b"));
176 cache.run_pending_tasks();
177 assert_eq!(cache.len(), 1);
178 }
179
180 #[test]
181 fn test_remove() {
182 let cache = SyncLru::new(2);
183
184 cache.put(1, "a");
185 cache.put(2, "b");
186 let removed = cache.remove(&1);
187
188 assert_eq!(removed, Some("a"));
189 assert_eq!(cache.get(&1), None);
190 cache.run_pending_tasks();
191 assert_eq!(cache.len(), 1);
192 }
193
194 #[test]
195 fn test_clear() {
196 let cache = SyncLru::new(2);
197
198 cache.put(1, "a");
199 cache.put(2, "b");
200 cache.clear();
201 cache.run_pending_tasks();
202
203 assert_eq!(cache.len(), 0);
204 assert!(cache.is_empty());
205 }
206
207 #[test]
208 fn test_contains_key() {
209 let cache = SyncLru::new(2);
210
211 cache.put(1, "a");
212 assert!(cache.contains_key(&1));
213 assert!(!cache.contains_key(&2));
214 }
215
216 #[test]
217 fn unmeasured_cache_reports_no_memory_usage() {
218 let cache: SyncLru<u64, String> = SyncLru::new(2);
219 cache.put(1, "a".to_string());
220 assert_eq!(cache.memory_usage(), None);
221 }
222
223 #[test]
224 fn measured_cache_counts_entries_heap_and_payload() {
225 let cache: SyncLru<u64, String> = SyncLru::measured(8, footprint);
226 let a = String::with_capacity(16) + "aaaa";
227 let b = String::with_capacity(32) + "bbbbbbbb";
228 let heap = a.capacity() + b.capacity();
229 let payload = (8 + a.len()) + (8 + b.len());
230
231 cache.put(1, a);
232 cache.put(2, b);
233 cache.run_pending_tasks();
234
235 let usage = cache.memory_usage().expect("measured cache must report usage");
236 assert_eq!(usage.entries, Count::new(2));
237 assert_eq!(usage.payload, ByteSize::from_bytes(payload as u64));
238 assert!(usage.resident.as_bytes() > heap as u64);
241 }
242
243 #[test]
244 fn replacing_a_key_keeps_single_entry_accounting() {
245 let cache: SyncLru<u64, String> = SyncLru::measured(8, footprint);
246 cache.put(1, "aaaa".to_string());
247 cache.put(1, "bbbbbbbb".to_string());
248 cache.run_pending_tasks();
249
250 let usage = cache.memory_usage().expect("measured cache must report usage");
251 assert_eq!(usage.entries, Count::new(1), "replacement must not leak the old entry's count");
252 assert_eq!(
253 usage.payload,
254 ByteSize::from_bytes(8 + 8),
255 "payload must reflect only the replacement value"
256 );
257 }
258
259 #[test]
260 fn removal_and_clear_release_accounted_memory() {
261 let cache: SyncLru<u64, String> = SyncLru::measured(8, footprint);
262 cache.put(1, "aaaa".to_string());
263 cache.put(2, "bbbb".to_string());
264 cache.remove(&1);
265 cache.run_pending_tasks();
266
267 let usage = cache.memory_usage().expect("measured cache must report usage");
268 assert_eq!(usage.entries, Count::new(1));
269 assert_eq!(usage.payload, ByteSize::from_bytes(8 + 4));
270
271 cache.clear();
272 cache.run_pending_tasks();
273
274 let usage = cache.memory_usage().expect("measured cache must report usage");
275 assert_eq!(usage.entries, Count::ZERO, "clear must release every accounted entry");
276 assert_eq!(usage.payload, ByteSize::ZERO);
277 assert_eq!(usage.resident, ByteSize::ZERO);
278 }
279
280 #[test]
281 fn eviction_at_capacity_releases_the_victims_memory() {
282 let cache: SyncLru<u64, String> = SyncLru::measured(2, footprint);
283 cache.put(1, "aaaa".to_string());
284 cache.put(2, "bbbb".to_string());
285 cache.run_pending_tasks();
286 cache.put(3, "cccc".to_string());
287 cache.run_pending_tasks();
288
289 let usage = cache.memory_usage().expect("measured cache must report usage");
290 assert_eq!(usage.entries, Count::new(2), "eviction must decrement the entry count");
291 assert_eq!(usage.payload, ByteSize::from_bytes(2 * (8 + 4)));
292 }
293
294 #[test]
295 fn measured_values_shared_via_arc_count_their_heap_once_per_slot() {
296 fn arc_footprint(_key: &u64, value: &Arc<str>) -> CacheFootprint {
297 CacheFootprint {
298 heap: 2 * size_of::<usize>() + value.len(),
299 payload: size_of::<u64>() + value.len(),
300 }
301 }
302 let cache: SyncLru<u64, Arc<str>> = SyncLru::measured(8, arc_footprint);
303 let shared: Arc<str> = Arc::from("shared-value");
304 cache.put(1, shared.clone());
305 cache.put(2, shared);
306 cache.run_pending_tasks();
307
308 let usage = cache.memory_usage().expect("measured cache must report usage");
309 assert_eq!(usage.entries, Count::new(2));
310 assert_eq!(usage.payload, ByteSize::from_bytes(2 * (8 + 12)));
311 }
312}