fuchsia_rcu/rcu_arc.rs
1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::rcu_ptr::RcuPtr;
6use crate::rcu_read_scope::RcuReadScope;
7use crate::state_machine::rcu_drop;
8use crate::subtle::rcu_ptr_upgrade;
9use std::sync::Arc;
10
11/// A version of [crate::RcuOptionArc] which does not require `T: RcuDroppable` in exchange for
12/// some extra atomic checking on reads.
13///
14/// RcuArc allows arbitrary types to be used with RCU by separating `Drop`ing the type,
15/// which may not be safe to do on the RCU advancer thread, and freeing the allocation for the
16/// type, which is safe to do, once an RCU grace period has elapsed.
17///
18/// We do the former by `Drop`ing the old `Arc<T>` synchronously on the calling thread when we
19/// do a replace. We do the latter by first placing a `Weak<T>` in the RCU callback queue to hold
20/// the memory alive (but with a potential strong count of zero) until an RCU grace period has
21/// elapsed.
22///
23/// Since the strong count of the memory may drop to zero even while a reader holds an
24/// RcuReadScope, readers must verify the memory is safe to read. Readers safely do this by using a
25/// Weak::upgrade, which serializes with a compare_exchange loop on the strong count.
26#[derive(Debug)]
27pub struct RcuArc<T: Send + Sync + 'static> {
28 ptr: RcuPtr<T>,
29}
30
31impl<T: Send + Sync + 'static> RcuArc<T> {
32 /// Create a new RcuArc from an `Option<Arc<T>>`.
33 pub fn new(data: impl Into<Option<Arc<T>>>) -> Self {
34 Self { ptr: RcuPtr::new(Self::into_ptr(data.into())) }
35 }
36
37 /// Read the contents of the RcuArc.
38 ///
39 /// Returns `None` if the wrapped Arc is `None` or is no longer valid.
40 pub fn upgrade(&self) -> Option<Arc<T>> {
41 let scope = RcuReadScope::new();
42 loop {
43 let ptr = self.ptr.read(&scope);
44 // If the pointer is null, we're done.
45 if ptr.is_null() {
46 return None;
47 }
48 // We could be racing with a call to [Self::update] here. If we get a non null pointer,
49 // but fail to upgrade the weak pointer, we simply retry. Because the writer updates
50 // `self.ptr` before dropping the old Arc, re-reading `self.ptr` will observe the new
51 // pointer (or null).
52
53 // SAFETY: `ptr` was created from an Arc::into_raw() or is null.
54 if let Some(arc) = unsafe { rcu_ptr_upgrade(ptr) } {
55 return Some(arc);
56 }
57 }
58 }
59
60 /// Write a new `Option<Arc<T>>` to the RcuArc.
61 ///
62 /// The old `Arc<T>` (if any) is dropped on the caller's thread, running `T::drop()`
63 /// synchronously if it was the last strong reference.
64 pub fn update(&self, data: impl Into<Option<Arc<T>>>) {
65 let ptr = Self::into_ptr(data.into());
66 // SAFETY: We pass a pointer obtained from `Self::into_ptr`.
67 unsafe { self.replace(ptr) };
68 }
69
70 /// Returns `true` if the RCU wrapper currently contains a value.
71 ///
72 /// Unlike `self.upgrade().is_some()`, this neither takes a transient strong reference nor
73 /// enters an `RcuReadScope`. A non-null pointer stored in the `RcuArc` always owns a strong
74 /// count, and the pointer is never dereferenced, so observing a non-null pointer is sufficient.
75 /// This guarantees that calling `is_some()` can never result in the caller running `T::drop()`,
76 /// which matters when `T::drop()` may acquire locks.
77 pub fn is_some(&self) -> bool {
78 !self.ptr.is_null()
79 }
80
81 /// Returns `true` if the RCU wrapper does not contain a value.
82 ///
83 /// See [`Self::is_some`] for why this does not upgrade.
84 pub fn is_none(&self) -> bool {
85 !self.is_some()
86 }
87
88 /// Extract the raw pointer from an `Option<Arc<T>>`.
89 fn into_ptr(data: Option<Arc<T>>) -> *mut T {
90 match data {
91 Some(arc) => Arc::into_raw(arc) as *mut T,
92 None => std::ptr::null_mut(),
93 }
94 }
95
96 /// Replace the pointer in the `RcuArc` with a new pointer.
97 ///
98 /// # Safety
99 ///
100 /// The caller must have obtained the pointer from `Self::into_ptr` or from `std::ptr::null_mut`.
101 unsafe fn replace(&self, ptr: *mut T) {
102 let old_ptr = self.ptr.replace(ptr);
103 if !old_ptr.is_null() {
104 // SAFETY: The caller ensures the pointer is obtained from Self::into_ptr or
105 // std::ptr::null_mut.
106 let old_arc = unsafe { Arc::from_raw(old_ptr) };
107 let weak = Arc::downgrade(&old_arc);
108 drop(old_arc);
109 rcu_drop(weak);
110 }
111 }
112}
113
114impl<T: Send + Sync + 'static> Drop for RcuArc<T> {
115 fn drop(&mut self) {
116 // SAFETY: We can pass `std::ptr::null_mut`.
117 unsafe { self.replace(std::ptr::null_mut()) };
118 }
119}
120
121impl<T: Send + Sync + 'static> Clone for RcuArc<T> {
122 fn clone(&self) -> Self {
123 Self::new(self.upgrade())
124 }
125}
126
127impl<T: Send + Sync + 'static> From<Option<Arc<T>>> for RcuArc<T> {
128 fn from(data: Option<Arc<T>>) -> Self {
129 Self::new(data)
130 }
131}
132
133impl<T: Send + Sync + 'static> From<Arc<T>> for RcuArc<T> {
134 fn from(data: Arc<T>) -> Self {
135 Self::new(Some(data))
136 }
137}
138
139impl<T: Send + Sync + 'static> Default for RcuArc<T> {
140 fn default() -> Self {
141 Self::new(None)
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148 use crate::state_machine::rcu_run_callbacks;
149 use std::sync::atomic::{AtomicUsize, Ordering};
150
151 // A struct that intentionally does NOT implement RcuDroppable
152 struct DropCounter {
153 value: usize,
154 drops: Arc<AtomicUsize>,
155 }
156
157 impl DropCounter {
158 pub fn new(value: usize, drops: Arc<AtomicUsize>) -> Arc<Self> {
159 Arc::new(Self { value, drops })
160 }
161 }
162
163 impl Drop for DropCounter {
164 fn drop(&mut self) {
165 self.drops.fetch_add(1, Ordering::Relaxed);
166 }
167 }
168
169 #[test]
170 fn test_arc_update_and_synchronous_drop() {
171 // We should see the Drop handler trigger before rcu_synchronize.
172 let drops = Arc::new(AtomicUsize::new(0));
173 let arc = RcuArc::new(Some(DropCounter::new(42, drops.clone())));
174
175 assert!(arc.is_some());
176 assert_eq!(arc.upgrade().unwrap().value, 42);
177 assert_eq!(drops.load(Ordering::Relaxed), 0);
178
179 arc.update(Some(DropCounter::new(43, drops.clone())));
180 assert_eq!(arc.upgrade().unwrap().value, 43);
181 assert_eq!(drops.load(Ordering::Relaxed), 1, "Drop must execute synchronously on update");
182
183 arc.update(None);
184 assert!(arc.is_none());
185 assert_eq!(drops.load(Ordering::Relaxed), 2, "Drop must execute synchronously on reset");
186
187 rcu_run_callbacks();
188 assert_eq!(drops.load(Ordering::Relaxed), 2);
189 }
190
191 #[test]
192 fn test_arc_default() {
193 let arc = RcuArc::<DropCounter>::default();
194 assert!(arc.is_none());
195 assert!(arc.upgrade().is_none());
196 }
197
198 #[test]
199 fn test_arc_clone() {
200 let drops = Arc::new(AtomicUsize::new(0));
201 let arc1 = RcuArc::new(Some(DropCounter::new(100, drops.clone())));
202 let arc2 = arc1.clone();
203
204 assert_eq!(arc1.upgrade().unwrap().value, 100);
205 assert_eq!(arc2.upgrade().unwrap().value, 100);
206
207 drop(arc1);
208 // arc2 still holds a strong reference to the value.
209 assert_eq!(drops.load(Ordering::Relaxed), 0);
210
211 drop(arc2);
212 assert_eq!(drops.load(Ordering::Relaxed), 1);
213 }
214}