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}