Skip to main content

rcu_128/
lib.rs

1#![cfg(target_has_atomic = "128")]
2#![feature(integer_atomics)]
3#![no_std]
4extern crate alloc;
5use alloc::boxed::Box;
6use parking_lot::RwLock;
7
8use core::{
9    hint,
10    marker::PhantomData,
11    ops::Deref,
12    ptr::NonNull,
13    sync::atomic::{AtomicU128, Ordering},
14};
15
16const COUNTER_MASK: u128 = 0xffff_ffff_ffff_ffff;
17
18/// Exponential backoff for spin-wait loops.
19///
20/// Starts with `spin_loop` hints, then yields the thread after a threshold
21/// to avoid burning CPU when waiting for long-lived readers.
22struct Backoff {
23    step: u32,
24}
25
26impl Backoff {
27    fn new() -> Self {
28        Self { step: 0 }
29    }
30
31    fn spin(&mut self) {
32        for _ in 0..1u32 << self.step.min(6) {
33            hint::spin_loop();
34        }
35        if self.step <= 6 {
36            self.step += 1;
37        }
38    }
39}
40
41/// A guard that provides read access to a value in an `RcuCell`.
42///
43/// When this guard is dropped, it will signal that the read operation
44/// is complete, allowing the `RcuCell` to manage its internal state
45/// accordingly.
46#[derive(Debug)]
47pub struct RcuGuard<'a, T> {
48    ptr: NonNull<T>,
49    cell: &'a RcuCell<T>,
50}
51
52impl<T> Deref for RcuGuard<'_, T> {
53    type Target = T;
54    fn deref(&self) -> &T {
55        // SAFETY: The pointer was obtained from a valid Box allocation in RcuCell::new,
56        // write, or update. The value is kept alive because:
57        // - If the value is still current (in ptr_counter_latest), it won't be freed
58        //   until swapped out and all readers drain.
59        // - If swapped out (in ptr_counter_to_clear), the writer spins until our
60        //   guard's counter decrement, so the value is alive while this guard exists.
61        unsafe { self.ptr.as_ref() }
62    }
63}
64
65impl<T> Drop for RcuGuard<'_, T> {
66    fn drop(&mut self) {
67        // Try to decrement ptr_counter_latest first (fast path: value hasn't been swapped)
68        let mut backoff = Backoff::new();
69        loop {
70            let ptr_counter = self.cell.ptr_counter_latest.load(Ordering::Acquire);
71            if (ptr_counter >> 64) as usize == self.ptr.as_ptr() as usize {
72                if self
73                    .cell
74                    .ptr_counter_latest
75                    .compare_exchange_weak(
76                        ptr_counter,
77                        ptr_counter - 1,
78                        Ordering::AcqRel,
79                        Ordering::Relaxed,
80                    )
81                    .is_ok()
82                {
83                    return;
84                }
85            } else {
86                // ptr_counter_latest has been updated, so we can't decrement it
87                break;
88            }
89            backoff.spin();
90        }
91        // Slow path: value was swapped out, decrement ptr_counter_to_clear.
92        // The writer that swapped our value out will (or has already) moved it
93        // into ptr_counter_to_clear. We spin until it appears there.
94        let mut backoff = Backoff::new();
95        loop {
96            let ptr_counter = self.cell.ptr_counter_to_clear.load(Ordering::Acquire);
97            if (ptr_counter >> 64) as usize == self.ptr.as_ptr() as usize
98                && self
99                    .cell
100                    .ptr_counter_to_clear
101                    .compare_exchange_weak(
102                        ptr_counter,
103                        ptr_counter - 1,
104                        Ordering::AcqRel,
105                        Ordering::Relaxed,
106                    )
107                    .is_ok()
108            {
109                return;
110            }
111            backoff.spin();
112        }
113    }
114}
115
116/// A concurrent data structure that allows for safe, read-copy-update (RCU)
117/// style access to its value.
118///
119/// # Grace period serialization
120///
121/// Only one old value can be pending reclamation at a time (single
122/// `ptr_counter_to_clear` slot). If multiple writers call [`write`](RcuCell::write)
123/// concurrently while readers hold guards to old values, their grace periods
124/// are serialized. This is acceptable for read-heavy workloads but can cause
125/// writer stalls under heavy write contention with long-lived readers.
126#[derive(Debug)]
127pub struct RcuCell<T> {
128    ptr_counter_latest: AtomicU128,
129    ptr_counter_to_clear: AtomicU128,
130    data: PhantomData<T>,
131    update_token: RwLock<()>,
132}
133
134impl<T: Default> Default for RcuCell<T> {
135    fn default() -> Self {
136        Self::new(Default::default())
137    }
138}
139
140impl<T> Drop for RcuCell<T> {
141    fn drop(&mut self) {
142        // SAFETY: All RcuGuards borrow &RcuCell, so the borrow checker guarantees
143        // they are all dropped before this runs. Therefore the counter is 0 and we
144        // have exclusive ownership of the value. get_mut() is sound because &mut self.
145        let ptr = (*self.ptr_counter_latest.get_mut() >> 64) as usize as *mut T;
146        unsafe {
147            let _ = Box::from_raw(ptr);
148        }
149    }
150}
151
152impl<T> RcuCell<T> {
153    /// Creates a new `RcuCell` with the given initial value.
154    ///
155    /// # Example
156    ///
157    /// ```
158    /// let rcu_cell = rcu_128::RcuCell::new(42);
159    /// ```
160    pub fn new(value: T) -> Self {
161        Self {
162            ptr_counter_latest: AtomicU128::new((Box::into_raw(Box::new(value)) as u128) << 64),
163            ptr_counter_to_clear: AtomicU128::new(0),
164            data: PhantomData,
165            update_token: RwLock::new(()),
166        }
167    }
168
169    /// Provides read access to the value stored in the `RcuCell`.
170    ///
171    /// This function returns an `RcuGuard`, which allows for safe,
172    /// concurrent read access to the `RcuCell`'s value.
173    ///
174    /// Once all `RcuGuard` instances referencing a particular value are
175    /// dropped, the value can be safely released during an update or write.
176    ///
177    /// # Example
178    ///
179    /// ```
180    /// let rcu_cell = rcu_128::RcuCell::new(42);
181    /// {
182    ///     let guard = rcu_cell.read();
183    ///     assert_eq!(*guard, 42);
184    /// }
185    /// ```
186    pub fn read(&self) -> RcuGuard<'_, T> {
187        // SAFETY: The upper 64 bits of ptr_counter_latest always hold a valid,
188        // non-null pointer to a Box-allocated T. fetch_add atomically increments
189        // the reader count, which prevents the writer from freeing this value
190        // until we drop the guard.
191        let ptr = unsafe {
192            NonNull::new_unchecked(
193                (self.ptr_counter_latest.fetch_add(1, Ordering::AcqRel) >> 64) as usize as *mut T,
194            )
195        };
196        RcuGuard { cell: self, ptr }
197    }
198
199    /// Writes a new value into the `RcuCell`.
200    ///
201    /// The new value becomes immediately visible to subsequent readers.
202    /// This method blocks until all readers of the old value have dropped
203    /// their guards, then frees the old value.
204    ///
205    /// Multiple concurrent `write` calls are allowed (last-writer-wins).
206    /// Use [`update`](RcuCell::update) if you need read-modify-write semantics.
207    ///
208    /// # Example
209    ///
210    /// ```
211    /// let rcu_cell = rcu_128::RcuCell::new(42);
212    /// rcu_cell.write(100);
213    /// {
214    ///     let guard = rcu_cell.read();
215    ///     assert_eq!(*guard, 100);
216    /// }
217    /// ```
218    pub fn write(&self, value: T) {
219        let new_ptr_counter = (Box::into_raw(Box::new(value)) as u128) << 64;
220        let token_shared = self.update_token.read();
221        let old_ptr_counter = self
222            .ptr_counter_latest
223            .swap(new_ptr_counter, Ordering::AcqRel);
224        drop(token_shared);
225        self.clear(old_ptr_counter);
226    }
227
228    /// Updates the value stored in the `RcuCell` using a provided function.
229    ///
230    /// This function applies the given closure `f` to the current value,
231    /// replacing it with the returned value. The closure runs under an
232    /// exclusive lock, so concurrent `update` and `write` calls are
233    /// serialized — the closure is guaranteed to run exactly once.
234    ///
235    /// # Example
236    ///
237    /// ```
238    /// let rcu_cell = rcu_128::RcuCell::new(42);
239    /// rcu_cell.update(|&old_value| old_value + 1);
240    /// {
241    ///     let guard = rcu_cell.read();
242    ///     assert_eq!(*guard, 43);
243    /// }
244    /// ```
245    pub fn update(&self, f: impl FnOnce(&T) -> T) {
246        let token_exclusive = self.update_token.write();
247        // SAFETY: The exclusive lock prevents any concurrent write/update from
248        // swapping out ptr_counter_latest's pointer. Readers only obtain shared
249        // references (&T) via guards, so no mutable aliasing occurs. The pointer
250        // is valid because it was produced by Box::into_raw and hasn't been freed
251        // (clear() only frees values after they're swapped out of ptr_counter_latest).
252        let old_value =
253            unsafe { &*((self.ptr_counter_latest.load(Ordering::Acquire) >> 64) as *const T) };
254        let new_value = f(old_value);
255        let new_ptr_counter = (Box::into_raw(Box::new(new_value)) as u128) << 64;
256        let old_ptr_counter = self
257            .ptr_counter_latest
258            .swap(new_ptr_counter, Ordering::AcqRel);
259        drop(token_exclusive);
260        self.clear(old_ptr_counter);
261    }
262
263    /// Waits for all readers of the old value to finish, then frees it.
264    fn clear(&self, old_ptr_counter: u128) {
265        if old_ptr_counter & COUNTER_MASK == 0 {
266            // No readers — release memory directly.
267            // SAFETY: The pointer was produced by Box::into_raw. The counter is 0,
268            // meaning no guards hold this pointer (the swap was atomic with the
269            // reader's fetch_add, so any reader that incremented the count is
270            // reflected here). It is safe to reclaim.
271            unsafe {
272                let _ = Box::from_raw((old_ptr_counter >> 64) as usize as *mut T);
273            }
274            return;
275        }
276
277        // Acquire the single reclamation slot. Only one old value can be
278        // pending in ptr_counter_to_clear at a time. Other writers spin here
279        // until the slot is available (grace period serialization).
280        let mut backoff = Backoff::new();
281        while self
282            .ptr_counter_to_clear
283            .compare_exchange_weak(0, old_ptr_counter, Ordering::AcqRel, Ordering::Relaxed)
284            .is_err()
285        {
286            // Inner loop: read-only spin to avoid exclusive cache line access (MESI)
287            while self.ptr_counter_to_clear.load(Ordering::Relaxed) != 0 {
288                backoff.spin();
289            }
290        }
291
292        // Wait for all readers of the old value to drop their guards.
293        // Each guard drop decrements the counter in ptr_counter_to_clear.
294        // No CAS needed here: once counter reaches 0, no other thread will
295        // modify it (new readers get the latest pointer, not this one).
296        let mut backoff = Backoff::new();
297        while self.ptr_counter_to_clear.load(Ordering::Acquire) & COUNTER_MASK != 0 {
298            backoff.spin();
299        }
300        // Clear the slot to allow other writers to reclaim their old values.
301        self.ptr_counter_to_clear.store(0, Ordering::Release);
302        // SAFETY: All readers have drained (counter == 0). The pointer was
303        // produced by Box::into_raw and has not been freed elsewhere.
304        unsafe {
305            let _ = Box::from_raw((old_ptr_counter >> 64) as usize as *mut T);
306        }
307    }
308}
309
310#[cfg(test)]
311mod tests {
312    extern crate std;
313    use super::*;
314    extern crate alloc;
315    use alloc::sync::Arc;
316    use alloc::vec::Vec;
317    use std::thread;
318
319    #[test]
320    fn basic_read_write() {
321        let cell = RcuCell::new(42);
322        assert_eq!(*cell.read(), 42);
323
324        cell.write(100);
325        assert_eq!(*cell.read(), 100);
326    }
327
328    #[test]
329    fn update_applies_closure() {
330        let cell = RcuCell::new(10);
331        cell.update(|&v| v + 5);
332        assert_eq!(*cell.read(), 15);
333
334        cell.update(|&v| v * 2);
335        assert_eq!(*cell.read(), 30);
336    }
337
338    #[test]
339    fn default_trait() {
340        let cell: RcuCell<i32> = RcuCell::default();
341        assert_eq!(*cell.read(), 0);
342    }
343
344    #[test]
345    fn multiple_guards_same_value() {
346        let cell = RcuCell::new(42);
347        let g1 = cell.read();
348        let g2 = cell.read();
349        let g3 = cell.read();
350        assert_eq!(*g1, 42);
351        assert_eq!(*g2, 42);
352        assert_eq!(*g3, 42);
353        drop(g1);
354        drop(g2);
355        drop(g3);
356    }
357
358    #[test]
359    fn guard_sees_value_at_read_time() {
360        let cell = Arc::new(RcuCell::new(1));
361        let guard = cell.read();
362
363        // Write from another thread (write blocks until guard drops)
364        let cell2 = cell.clone();
365        let handle = thread::spawn(move || {
366            cell2.write(2);
367        });
368
369        // Guard still sees the old value
370        assert_eq!(*guard, 1);
371        drop(guard);
372        handle.join().unwrap();
373        assert_eq!(*cell.read(), 2);
374    }
375
376    #[test]
377    fn drop_frees_value() {
378        // Use Arc to verify the value is freed when RcuCell is dropped.
379        let inner = Arc::new(42);
380        let cell = RcuCell::new(inner.clone());
381        assert_eq!(Arc::strong_count(&inner), 2);
382        drop(cell);
383        assert_eq!(Arc::strong_count(&inner), 1);
384    }
385
386    #[test]
387    fn write_frees_old_value() {
388        let v1 = Arc::new(1);
389        let v2 = Arc::new(2);
390        let cell = RcuCell::new(v1.clone());
391        assert_eq!(Arc::strong_count(&v1), 2);
392
393        cell.write(v2.clone());
394        // old value should be freed since no guards held it
395        assert_eq!(Arc::strong_count(&v1), 1);
396        assert_eq!(Arc::strong_count(&v2), 2);
397    }
398
399    #[test]
400    #[cfg(not(miri))] // requires threads + spin-wait, too slow for Miri
401    fn old_value_freed_after_guard_drop() {
402        let v1 = Arc::new(1);
403        let cell = RcuCell::new(v1.clone());
404        let guard = cell.read();
405        assert_eq!(Arc::strong_count(&v1), 2);
406
407        // Spawn a thread to write, which will block until we drop the guard
408        let cell_ref = &cell;
409        let v2 = Arc::new(2);
410        let v2_clone = v2.clone();
411        thread::scope(|s| {
412            s.spawn(move || {
413                cell_ref.write(v2_clone);
414            });
415            // Give the writer time to swap (but it will spin on clear)
416            thread::sleep(std::time::Duration::from_millis(10));
417            // v1 still alive because guard holds it
418            assert_eq!(Arc::strong_count(&v1), 2);
419            drop(guard);
420        });
421        // After scope, writer thread joined, v1 should be freed
422        assert_eq!(Arc::strong_count(&v1), 1);
423        assert_eq!(*cell.read(), v2);
424    }
425
426    #[test]
427    #[cfg(not(miri))]
428    fn concurrent_readers() {
429        let cell = Arc::new(RcuCell::new(0u64));
430        let mut handles = Vec::new();
431        for _ in 0..4 {
432            let cell = cell.clone();
433            handles.push(thread::spawn(move || {
434                for _ in 0..1000 {
435                    let guard = cell.read();
436                    let _ = *guard; // just read
437                }
438            }));
439        }
440        for h in handles {
441            h.join().unwrap();
442        }
443    }
444
445    #[test]
446    #[cfg(not(miri))]
447    fn concurrent_read_write() {
448        let cell = Arc::new(RcuCell::new(0u64));
449
450        thread::scope(|s| {
451            // Writer
452            let cell_w = cell.clone();
453            s.spawn(move || {
454                for i in 0..100 {
455                    cell_w.write(i);
456                }
457            });
458
459            // Readers
460            for _ in 0..4 {
461                let cell_r = cell.clone();
462                s.spawn(move || {
463                    for _ in 0..1000 {
464                        let guard = cell_r.read();
465                        let val = *guard;
466                        assert!(val < 100);
467                    }
468                });
469            }
470        });
471    }
472
473    #[test]
474    #[cfg(not(miri))]
475    fn concurrent_updates() {
476        let cell = Arc::new(RcuCell::new(0u64));
477
478        thread::scope(|s| {
479            for _ in 0..4 {
480                let cell = cell.clone();
481                s.spawn(move || {
482                    for _ in 0..100 {
483                        cell.update(|&v| v + 1);
484                    }
485                });
486            }
487        });
488
489        assert_eq!(*cell.read(), 400);
490    }
491
492    #[test]
493    #[cfg(not(miri))]
494    fn stress_mixed_operations() {
495        let cell = Arc::new(RcuCell::new(0u64));
496
497        thread::scope(|s| {
498            // Writers
499            for _ in 0..2 {
500                let cell = cell.clone();
501                s.spawn(move || {
502                    for i in 0..20 {
503                        cell.write(i);
504                    }
505                });
506            }
507
508            // Updaters
509            for _ in 0..2 {
510                let cell = cell.clone();
511                s.spawn(move || {
512                    for _ in 0..20 {
513                        cell.update(|&v| v.wrapping_add(1));
514                    }
515                });
516            }
517
518            // Readers with held guards
519            for _ in 0..2 {
520                let cell = cell.clone();
521                s.spawn(move || {
522                    let mut guards = Vec::new();
523                    for i in 0..80 {
524                        guards.push(cell.read());
525                        if guards.len() > 4 {
526                            guards.remove(0);
527                        }
528                        if i % 10 == 0 {
529                            guards.clear();
530                        }
531                    }
532                });
533            }
534        });
535    }
536}