Files
concurrent-linked-list/src/double/mod.rs
2025-07-20 01:36:48 +02:00

246 lines
7.9 KiB
Rust

//! Doubly non-Arc linked list.
//!
//! Doubly as each node points to the next and previous node.
use std::{marker::PhantomPinned, mem::transmute, ops::Deref, pin::Pin};
use parking_lot::RwLock;
use crate::double::node::{BackNodeWriteLock, Node, NodeBackPtr};
pub mod node;
// # Rules to prevent deadlocks
//
// Left locking must be `try_` and if it fails at any point, the way rightwards must be cleared in
// case the task holding the left lock is moving rightwards.
// Rightwards locking can be blocking.
pub struct NodeHeadInner<'ll, T> {
start: Option<&'ll node::Node<'ll, T>>,
}
impl<T> Default for NodeHeadInner<'_, T> {
fn default() -> Self {
Self { start: None }
}
}
pub struct NodeHead<'ll, T>(RwLock<NodeHeadInner<'ll, T>>);
impl<T> Default for NodeHead<'_, T> {
#[must_use]
fn default() -> Self {
Self(RwLock::new(NodeHeadInner::default()))
}
}
impl<'ll, T> Deref for NodeHead<'ll, T> {
type Target = RwLock<NodeHeadInner<'ll, T>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'ll, T> NodeHead<'ll, T> {
#[must_use]
pub fn new() -> Self {
Self::default()
}
/// # Safety
///
/// The only context in which it's safe to call this when [`self`] was pinned immediately after
/// its creation so that it can be guaranteed that the returned reference is valid for all the
/// [`LinkedList`]'s lifetime.
///
/// The issue arises from the [`Drop`] implementation. It takes a `&mut` reference, that means
/// that all previous immutable references are dropped. But most methods of the linked require
/// you to promise the borrow of [`self`] is valid for all `'ll` and that's only true if no
/// destructor runs. This makes [`Drop`] incompatible with the use of methods of the form
/// `fn(&'myself self)`.
///
/// In turn to this, the [`Drop`] implementation must assume it's not the only reference even
/// thought it's `&mut`. Anyhow it should only be called when the scope of [`self`] is about to
/// end and the other references would be invalidated after the call to [`Drop`], though in
/// reality they really have no use before that call but to guarantee the "internal" self
/// references in the linked list remain valid through the destructor. It's kinda a really edge
/// case in the language with shared structures that require references with lifetimes as long
/// as self to guarantee their validity but still have [`Drop`] implementations.
#[must_use]
pub unsafe fn get_self_ref(myself: Pin<&Self>) -> &'ll Self {
unsafe { transmute::<&Self, &'ll Self>(myself.get_ref()) }
}
/// # Safety
///
/// Must be at the end at the end of its scope, when there's no references into it left.
pub unsafe fn manual_drop(&'ll self) {
// SAFETY: this is the drop impl so we can guarantee the reference is valid for the
// lifetime of the struct itself and external references would be invalidated right after
// the [`Drop`] of it (this fn). I don't think there's a way to differenciate the lifetimes
// by a drop implementation so this would be safe as no external references lifetimes would
// be valid after drop finishes
while unsafe { self.pop().is_some() } {}
}
pub fn prepend(&'ll self, data: T) {
let self_lock = self.write();
let next = self_lock.start;
let next_lock = next.map(|n| n.write());
let new_node = Node::new_leaked(data, NodeBackPtr::new_head(self), next);
// SAFETY: ptrs are surrounding and they've been locked all along
unsafe { new_node.integrate((BackNodeWriteLock::Head(self_lock), next_lock)) };
}
/// Returns [`None`] if there's no next node.
///
/// # Safety
///
/// There must be no outer references to the first node.
pub unsafe fn pop(&'ll self) -> Option<()> {
let self_lock = self.write();
let pop_node = self_lock.start?;
let pop_node_lock = pop_node.write();
let next_node_lock = pop_node_lock.next.map(|n| n.write());
// SAFETY: locked all along and consecutive nodes
unsafe {
Node::isolate(
&pop_node_lock,
(BackNodeWriteLock::Head(self_lock), next_node_lock),
);
}
drop(pop_node_lock);
// SAFETY: node has been isolated so no references out
unsafe { pop_node.wait_free() }
Some(())
}
pub fn clone_into_vec(&self) -> Vec<T>
where
T: Clone,
{
let mut total = Vec::new();
let mut next_node = self.read().start;
while let Some(node) = next_node {
let read = node.read();
total.push(read.data.clone());
next_node = read.next;
}
total
}
}
// Can't quite make this work how I want 😭
/// Attempt to safe wrap around a [`NodeHead`] to allow a sound API with [`Drop`] implementation.
///
/// Please see [`create_ll`] and [`del_ll`]
pub struct LinkedList<'ll, T> {
head: NodeHead<'ll, T>,
_pinned: PhantomPinned,
}
impl<'ll, T> LinkedList<'ll, T> {
#[must_use]
pub fn new() -> Self {
Self::default()
}
/// # Safety
///
/// NEVER EVER EVER extend the lifetime of this beyond the end of scope of the linked list
/// itself, it's all good and safe UNTIL it's dropped.
///
/// Allows you to associate the lifetime of this to something else to prevent accidentall
/// over-extending the lifetime.
pub unsafe fn extend_for<'scope>(&self, _: &'scope impl std::any::Any) -> &'ll NodeHead<'ll, T>
where
'scope: 'll,
{
unsafe { transmute(&self.head) }
}
}
impl<T> Default for LinkedList<'_, T> {
#[must_use]
fn default() -> Self {
Self {
head: NodeHead::new(),
_pinned: PhantomPinned,
}
}
}
impl<T> Drop for LinkedList<'_, T> {
fn drop(&mut self) {
// Extend to 'static so the compiler doesn't cry, we know this covers 'll
let myself = unsafe { self.extend_for(&()) };
// And this is `Drop` so there shouldn't be any refs as the end of this function would be
// where their lifetime ('ll) ends
unsafe { myself.manual_drop() }
}
}
// I'm not so sure this is that much bulletproof but behaves sollidly enough
/// Unsafe macro that automatically creates a linked list and an extended reference.
///
/// Also creates a `scope = ()` variable at the same level as `$val` to mimic its scope and prevent
/// accidental expansion of the unsafe lifetime beyond the current scope.
///
/// For example:
///
/// ```
/// use concurrent_linked_list::double::{create_ll, del_ll, LinkedList};
///
/// create_ll!(LinkedList::<String>::new(), ll_val, ll);
/// ll.prepend("test".to_string());
/// del_ll!(ll_val, ll);
/// ```
///
/// But trying to use `ll` after the deletion should fail:
///
/// ```compile_fail
/// use concurrent_linked_list::double::{create_ll, del_ll, LinkedList};
///
/// create_ll!(LinkedList::<String>::new(), ll_val, ll);
/// ll.prepend("test".to_string());
/// del_ll!(ll_val, ll);
/// ll.prepend("test2".to_string());
/// ```
///
/// Or trying to expand `ll` beyond the scope it was defined in, even if not deleted:
///
/// ```compile_fail
/// use concurrent_linked_list::double::{create_ll, del_ll, LinkedList};
///
/// let ll = {
/// create_ll!(LinkedList::<String>::new(), ll_val, ll);
/// ll.prepend("test".to_string());
/// ll
/// }
/// ll.prepend("test2".to_string());
/// ```
pub macro create_ll($rhs:expr, $val:ident, $ref:ident) {
let $val = $rhs;
let scope = ();
let $ref = unsafe { $val.extend_for(&scope) };
}
/// Macro that attempts to run some higene cleanup on [`create_ll`] to avoid accidental use ot the
/// reference further too. Other functions could still extend the lifetime beyond acceptable
/// though.
pub macro del_ll($val:ident, $ref:ident) {
#[allow(unused_variables)]
let $ref = ();
drop($val);
}
#[cfg(test)]
mod tests;