//! Least recently used (LRU) cache //! //! Implements a cache with least recently used cache replacement policy. //! A HashMap is used for fast access by a given key and a doubly linked list //! is used to keep track of the cache access order. use std::collections::HashMap; /// Node of the doubly linked list storing key and value struct CacheNode { // We need to additionally store the key to be able to remove it // from the HashMap when removing the tail. key: u64, value: V, prev: *mut CacheNode, next: *mut CacheNode, } impl CacheNode { fn new(key: u64, value: V) -> Self { Self { key, value, prev: std::ptr::null_mut(), next: std::ptr::null_mut(), } } } /// LRU cache instance. /// /// # Examples: /// ``` /// # use self::proxmox_backup::tools::lru_cache::LruCache; /// # fn main() -> Result<(), failure::Error> { /// let mut cache = LruCache::new(3); /// /// assert_eq!(cache.get_mut(1), None); /// assert_eq!(cache.len(), 0); /// /// cache.insert(1, 1); /// cache.insert(2, 2); /// cache.insert(3, 3); /// cache.insert(4, 4); /// assert_eq!(cache.len(), 3); /// /// assert_eq!(cache.get_mut(1), None); /// assert_eq!(cache.get_mut(2), Some(&mut 2)); /// assert_eq!(cache.get_mut(3), Some(&mut 3)); /// assert_eq!(cache.get_mut(4), Some(&mut 4)); /// /// cache.remove(4); /// cache.remove(3); /// cache.remove(2); /// assert_eq!(cache.len(), 0); /// assert_eq!(cache.get_mut(2), None); /// /// cache.insert(1, 1); /// assert_eq!(cache.get_mut(1), Some(&mut 1)); /// /// cache.clear(); /// assert_eq!(cache.len(), 0); /// assert_eq!(cache.get_mut(1), None); /// # Ok(()) /// # } /// ``` pub struct LruCache { map: HashMap>, head: *mut CacheNode, tail: *mut CacheNode, capacity: usize, } impl LruCache { /// Create LRU cache instance which holds up to `capacity` nodes at once. pub fn new(capacity: usize) -> Self { Self { map: HashMap::with_capacity(capacity), head: std::ptr::null_mut(), tail: std::ptr::null_mut(), capacity, } } /// Clear all the entries from the cache. pub fn clear(&mut self) { // Dump all heap allocations, then dump all the pointers in the HashMap for node_ptr in self.map.values() { unsafe { Box::from_raw(*node_ptr) }; } self.map.clear(); // Reset head and tail pointers self.head = std::ptr::null_mut(); self.tail = std::ptr::null_mut(); } /// Insert or update an entry identified by `key` with the given `value`. /// This entry is placed as the most recently used node at the head. pub fn insert(&mut self, key: u64, value: V) { match self.get_mut(key) { // Key already exists and get_mut brings node to the front, so only update its value. Some(old_val) => *old_val = value, None => { // If we have more elements than capacity, delete the tail entry // (= oldest entry). if self.map.len() >= self.capacity { self.remove_tail(); } self.insert_front(key, value); } } } /// Insert a key, value pair at the front of the linked list and it's pointer /// into the HashMap. fn insert_front(&mut self, key: u64, value: V) { // First create heap allocated `CacheNode` containing value. let mut node = Box::new(CacheNode::new(key, value)); // Old head gets new heads next node.next = self.head; // Release ownership of node, rest can be handled with just the pointer. let node_ptr = Box::into_raw(node); // Update the prev for the old head if !self.head.is_null() { unsafe { (*self.head).prev = node_ptr }; } // Update the head to the new node pointer self.head = node_ptr; // If there was no old tail, this node will be the new tail too if self.tail.is_null() { self.tail = node_ptr; } // finally insert the node pointer into the HashMap self.map.insert(key, node_ptr); } /// Remove the given `key` and its `value` from the cache. pub fn remove(&mut self, key: u64) -> Option { // Remove node pointer from the HashMap and get ownership of the node let node_ptr = self.map.remove(&key)?; let node = unsafe { Box::from_raw(node_ptr) }; // Update the previous node or otherwise the head if !node.prev.is_null() { unsafe { (*node.prev).next = node.next }; } else { // No previous node means this was the head self.head = node.next; } // Update the next node or otherwise the tail if !node.next.is_null() { unsafe { (*node.next).prev = node.prev }; } else { // No next node means this was the tail self.tail = node.prev; } Some(node.value) } /// Remove the least recently used node from the cache. fn remove_tail(&mut self) { if self.tail.is_null() { panic!("Called remove_tail on empty tail pointer!"); } let old_tail = unsafe { Box::from_raw(self.tail) }; self.tail = old_tail.prev; // Update next node for new tail if !self.tail.is_null() { unsafe { (*self.tail).next = std::ptr::null_mut() }; } // Remove HashMap entry for old tail self.map.remove(&old_tail.key); } /// Get a mutable reference to the value identified by `key`. /// This will update the cache entry to be the most recently used entry. pub fn get_mut<'a>(&'a mut self, key: u64) -> Option<&'a mut V> { let node_ptr = self.map.get(&key)?; if *node_ptr == self.head { // node is already head, just return return Some(unsafe { &mut (*self.head).value }); } // Update the prev node to point to next (or null if current node is tail) let mut node = unsafe { Box::from_raw(*node_ptr) }; unsafe { (*node.prev).next = node.next }; // Update the next node or otherwise the tail if !node.next.is_null() { unsafe { (*node.next).prev = node.prev }; } else { // No next node means this was the tail self.tail = node.prev; } node.prev = std::ptr::null_mut(); node.next = self.head; // update the head and release ownership of the node again let node_ptr = Box::into_raw(node); // Update current head unsafe { (*self.head).prev = node_ptr }; // Update to new head self.head = node_ptr; Some(unsafe { &mut (*self.head).value }) } /// Number of entries in the cache. pub fn len(&self) -> usize { self.map.len() } }