atomic_lend_cell/
flag_based.rs

1// Allow dead code when the flag-based feature is not enabled
2#![cfg_attr(not(feature = "flag-based"), allow(dead_code))]
3
4//! # Atomic Lend Cell
5//! 
6//! A thread-safe container that allows lending references to data across threads
7//! using epoch-based reclamation for safety verification without per-object reference counting.
8//! 
9//! This module provides two main types:
10//! - `AtomicLendCell<T>`: The owner that contains the data and can lend it out
11//! - `AtomicBorrowCell<T>`: A lightweight borrow of data that can be freely sent between threads
12//!
13//! Unlike atomic reference counting, this implementation uses a single boolean flag
14//! to track the owner's lifetime, reducing synchronization overhead while still
15//! ensuring safety.
16
17use std::{ops::Deref, sync::atomic::{AtomicBool, Ordering}};
18
19/// A container that allows thread-safe lending of its contained value using epoch-based reclamation
20///
21/// `AtomicLendCell<T>` owns a value of type `T` and maintains an atomic boolean
22/// to track its lifetime. It ensures that the value isn't accessed after being dropped,
23/// with validation occurring in debug builds.
24pub struct AtomicLendCell<T> {
25    data: T,
26    is_alive: AtomicBool
27}
28
29impl<T> AtomicLendCell<T> {
30    /// Returns a reference to the contained value
31    ///
32    /// This method provides direct access to the value inside the cell without
33    /// creating a borrowing relationship.
34    pub fn as_ref(&self) -> &T {
35        &self.data
36    }
37}
38
39impl<T> Deref for AtomicLendCell<T> {
40    type Target = T;
41    /// Dereferences to the contained value
42    ///
43    /// This provides convenient access to the contained value through the dereference operator (*).
44    fn deref(&self) -> &Self::Target {
45        self.as_ref()
46    }
47}
48
49impl<T> Drop for AtomicLendCell<T> {
50    /// Marks the cell as no longer alive when it's dropped
51    ///
52    /// This allows borrows to detect if they're being used after the owner was dropped.
53    fn drop(&mut self) {
54        // Mark as no longer alive
55        self.is_alive.store(false, Ordering::Release);
56        
57        // Optional: Give in-flight operations a chance to complete
58        #[cfg(debug_assertions)]
59        std::thread::yield_now();
60    }
61}
62
63/// A thread-safe reference to data contained in an `AtomicLendCell`
64///
65/// `AtomicBorrowCell<T>` holds a pointer to data in an `AtomicLendCell<T>` and
66/// checks the lender's liveness in debug builds. It can be safely sent between threads.
67pub struct AtomicBorrowCell<T> {
68    data_ptr: *const T,
69    owner_alive_ptr: *const AtomicBool
70}
71
72impl<T> AtomicBorrowCell<T> {
73    /// Returns a reference to the borrowed value
74    ///
75    /// This method provides access to the value inside the original `AtomicLendCell`.
76    /// In debug builds, it verifies that the owner is still alive.
77    pub fn as_ref(&self) -> &T {
78        #[cfg(debug_assertions)]
79        {
80            let is_alive = unsafe { self.owner_alive_ptr.as_ref().unwrap() }
81                .load(Ordering::Acquire);
82            if !is_alive {
83                panic!("Attempting to access AtomicBorrowCell after owner was dropped");
84            }
85        }
86        
87        unsafe { self.data_ptr.as_ref().unwrap() }
88    }
89}
90
91impl<T> Deref for AtomicBorrowCell<T> {
92    type Target = T;
93    /// Dereferences to the borrowed value
94    ///
95    /// This provides convenient access to the borrowed value through the dereference operator (*).
96    fn deref(&self) -> &Self::Target {
97        self.as_ref()
98    }
99}
100
101impl<T> Drop for AtomicBorrowCell<T> {
102    /// Checks if the owner is still alive when this borrow is dropped
103    ///
104    /// In debug builds, this will panic if the borrow is dropped after the owner,
105    /// helping to detect potential use-after-free bugs.
106    fn drop(&mut self) {
107        #[cfg(debug_assertions)]
108        {
109            let is_alive = unsafe { self.owner_alive_ptr.as_ref().unwrap() }
110                .load(Ordering::Acquire);
111            if !is_alive {
112                // We were dropped after owner - this shouldn't happen in correct code
113                panic!("AtomicBorrowCell dropped after its owner was dropped");
114            }
115        }
116    }
117}
118
119// These trait implementations make `AtomicBorrowCell` safe to send between threads
120unsafe impl<T: Sync> Send for AtomicBorrowCell<T> {}
121unsafe impl<T: Sync> Sync for AtomicBorrowCell<T> {}
122
123impl<T> AtomicLendCell<T> {
124    /// Creates a new `AtomicLendCell` containing the given value
125    ///
126    /// # Examples
127    ///
128    /// ```
129    /// use atomic_lend_cell::AtomicLendCell;
130    ///
131    /// let cell = AtomicLendCell::new(42);
132    /// ```
133    pub fn new(data: T) -> Self {
134        Self { data, is_alive: AtomicBool::new(true) }
135    }
136
137    /// Creates a new `AtomicBorrowCell` for the contained value
138    ///
139    /// This returns a borrow that can be sent to other threads. The borrow will
140    /// verify the owner's liveness in debug builds.
141    ///
142    /// # Examples
143    ///
144    /// ```
145    /// use atomic_lend_cell::AtomicLendCell;
146    ///
147    /// let cell = AtomicLendCell::new(42);
148    /// let borrow = cell.borrow();
149    ///
150    /// assert_eq!(*borrow, 42);
151    /// ```
152    pub fn borrow(&self) -> AtomicBorrowCell<T> {
153        AtomicBorrowCell {
154            data_ptr: (&self.data) as *const T,
155            owner_alive_ptr: &self.is_alive as *const AtomicBool
156        }
157    }
158    
159}
160
161impl<'a, T> AtomicLendCell<&'a T> {
162    /// Creates a new `AtomicBorrowCell` that borrows the referenced value directly
163    ///
164    /// This is useful when the `AtomicLendCell` contains a reference, and you want to
165    /// borrow the underlying value rather than the reference itself.
166    pub fn borrow_deref(&'a self) -> AtomicBorrowCell<T> {
167        AtomicBorrowCell {
168            data_ptr: self.data as *const T,
169            owner_alive_ptr: &self.is_alive as *const AtomicBool
170        }
171    }
172}
173
174impl<T> Clone for AtomicBorrowCell<T> {
175    /// Creates a new `AtomicBorrowCell` that borrows the same value
176    ///
177    /// Unlike reference counting, this doesn't need to increment any counters,
178    /// making it more efficient.
179    fn clone(&self) -> Self {
180        // Simply create a new borrow pointing to the same data and liveness flag
181        AtomicBorrowCell {
182            data_ptr: self.data_ptr,
183            owner_alive_ptr: self.owner_alive_ptr
184        }
185    }
186}
187
188#[test]
189/// Tests that borrowing works across threads
190fn test_epoch_borrow() {
191    let x = AtomicLendCell::new(4);
192    let xr = x.borrow();
193    let t1 = std::thread::spawn(move || {
194        let y = xr.as_ref();
195        println!("{:?}", y);
196    });
197    let xr = x.borrow();
198    let t2 = std::thread::spawn(move || {
199        let y = xr.as_ref();
200        println!("{:?}", y);
201    });
202    t1.join().unwrap();
203    t2.join().unwrap();
204}
205
206#[test]
207/// Tests the safety checks for owner outliving borrows
208fn test_epoch_safety() {
209    use std::sync::Arc;
210    
211    // This test will only panic in debug builds
212    let data = Arc::new(42);
213    let data_clone = Arc::clone(&data);
214    
215    let x_opt = Some(AtomicLendCell::new(data));
216    let borrow = x_opt.as_ref().unwrap().borrow();
217    
218    // Use the borrow before dropping owner
219    assert_eq!(**borrow, 42);
220    
221    // Simulate work in another thread
222    let handle = std::thread::spawn(move || {
223        // Just hold onto data_clone to ensure it doesn't drop
224        assert_eq!(*data_clone, 42);
225        std::thread::sleep(std::time::Duration::from_millis(50));
226    });
227    
228    // Drop the owner while borrow still exists
229    drop(x_opt);
230    
231    // In debug builds, this would panic when checking borrow's liveness
232    #[cfg(not(debug_assertions))]
233    {
234        // This should only run in release builds
235        std::thread::sleep(std::time::Duration::from_millis(10));
236        
237        // This will cause undefined behavior in release mode if safety is violated
238        let _value = *borrow;
239    }
240    
241    handle.join().unwrap();
242}