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}