tools::lru_cache: Separate LinkedList part of LruCache into own struct.

In order to keep the separation more aparent and avoid mut borrow conflics.

Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
This commit is contained in:
Christian Ebner 2020-02-25 18:45:27 +01:00 committed by Dietmar Maurer
parent 75c2ee7bab
commit e3ab9a383c

View File

@ -272,3 +272,164 @@ impl<K: std::cmp::Eq + std::hash::Hash + Copy, V> LruCache<K, V> {
}
}
}
/// Linked list holding the nodes of the LruCache.
///
/// This struct actually holds the CacheNodes via the raw linked list pointers
/// and allows to define the access sequence of these via the list sequence.
/// The LinkedList of the standard library unfortunately does not implement
/// an efficient way to bring list entries to the front, therefore we need our own.
struct LinkedList<K, V> {
head: *mut CacheNode<K, V>,
tail: *mut CacheNode<K, V>,
}
impl<K, V> LinkedList<K, V> {
/// Create a new empty linked list.
fn new() -> Self {
Self {
head: std::ptr::null_mut(),
tail: std::ptr::null_mut(),
}
}
/// Bring the CacheNode referenced by `node_ptr` to the front of the linked list.
fn bring_to_front(&mut self, node_ptr: *mut CacheNode<K, V>) {
if node_ptr == self.head {
// node is already head, just return
return;
}
let mut node = unsafe { Box::from_raw(node_ptr) };
// Update the prev node to point to next (or null if current node is tail)
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;
}
/// Insert a new node at the front of the linked list.
fn push_front(&mut self, node_ptr: *mut CacheNode<K, V>) {
let mut node = unsafe { Box::from_raw(node_ptr) };
// 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;
}
}
/// Remove the node referenced by `node_ptr` from the linke list and return it.
fn remove(&mut self, node_ptr: *mut CacheNode<K, V>) -> Box<CacheNode<K, V>> {
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;
}
node
}
/// Remove the tail node from the linked list and return it.
fn pop_tail(&mut self) -> Option<Box<CacheNode<K, V>>> {
if self.tail.is_null() {
return None;
}
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() };
}
Some(old_tail)
}
/// Clear the linked list and free all the nodes.
fn clear(&mut self) {
let mut next = self.head;
while !next.is_null() {
// Taking ownership of node and drop it at the end of the block.
let current = unsafe { Box::from_raw(next) };
next = current.next;
}
// Reset head and tail pointers
self.head = std::ptr::null_mut();
self.tail = std::ptr::null_mut();
}
}
#[test]
fn test_linked_list() {
let mut list = LinkedList::new();
for idx in 0..3 {
let node = Box::new(CacheNode::new(idx, idx + 1));
// Get pointer, release ownership.
let node_ptr = Box::into_raw(node);
list.push_front(node_ptr);
}
assert_eq!(unsafe { (*list.head).key }, 2);
assert_eq!(unsafe { (*list.head).value }, 3);
assert_eq!(unsafe { (*list.tail).key }, 0);
assert_eq!(unsafe { (*list.tail).value }, 1);
list.bring_to_front(list.tail);
assert_eq!(unsafe { (*list.head).key }, 0);
assert_eq!(unsafe { (*list.head).value }, 1);
assert_eq!(unsafe { (*list.tail).key }, 1);
assert_eq!(unsafe { (*list.tail).value }, 2);
list.bring_to_front(list.tail);
assert_eq!(unsafe { (*list.head).key }, 1);
assert_eq!(unsafe { (*list.head).value }, 2);
assert_eq!(unsafe { (*list.tail).key }, 2);
assert_eq!(unsafe { (*list.tail).value }, 3);
let tail = list.pop_tail().unwrap();
assert_eq!(tail.key, 2);
assert_eq!(tail.value, 3);
assert_eq!(unsafe { (*list.head).key }, 1);
assert_eq!(unsafe { (*list.head).value }, 2);
assert_eq!(unsafe { (*list.tail).key }, 0);
assert_eq!(unsafe { (*list.tail).value }, 1);
list.clear();
assert!(list.head.is_null());
assert!(list.tail.is_null());
}