From 1685c2e3418411d7fb63c88f3dea5ff29095c0fe Mon Sep 17 00:00:00 2001 From: Christian Ebner Date: Tue, 21 Jan 2020 14:21:53 +0100 Subject: [PATCH] tools: LRU cache to use for pxar FUSE implementation. Implements a cache with least recently used cache replacement policy. Internally the state is tracked by a HashMap (for fast access) and a doubly linked list (for the access order). Signed-off-by: Christian Ebner --- src/tools.rs | 1 + src/tools/lru_cache.rs | 219 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 src/tools/lru_cache.rs diff --git a/src/tools.rs b/src/tools.rs index cca183bd..439252ab 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -23,6 +23,7 @@ pub mod async_io; pub mod borrow; pub mod daemon; pub mod fs; +pub mod lru_cache; pub mod runtime; pub mod ticket; pub mod timer; diff --git a/src/tools/lru_cache.rs b/src/tools/lru_cache.rs new file mode 100644 index 00000000..191a61a7 --- /dev/null +++ b/src/tools/lru_cache.rs @@ -0,0 +1,219 @@ +//! 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() + } +}