fuchsia_rcu_collections/rcu_intrusive_list.rs
1// Copyright 2025 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
5#![warn(unsafe_op_in_unsafe_fn)]
6
7use fuchsia_rcu::subtle::{RcuPtr, RcuPtrRef};
8use fuchsia_rcu::{RcuDroppable, RcuReadScope};
9
10/// `Link` is an intrusive structure in a doubly-linked list.
11///
12/// Links are address-sensitive and cannot be moved once inserted into a list.
13#[derive(Debug, RcuDroppable)]
14pub struct Link {
15 /// The next node in the list.
16 ///
17 /// This field can be used to traverse the list within an RcuReadScope.
18 next: RcuPtr<Link>,
19
20 /// The previous node in the list.
21 ///
22 /// Poisoned (`std::ptr::dangling_mut()`) when the node is unattached, null when the node is
23 /// the head of a list, and points to the previous node's `Link` otherwise.
24 ///
25 /// This pointer cannot be used without external synchronization.
26 prev: RcuPtr<Link>,
27}
28
29impl Default for Link {
30 fn default() -> Self {
31 // Initialize `prev` to the poisoned (dangling) state so newly created unattached
32 // links match links detached via `remove` or `clear`.
33 Self { next: RcuPtr::null(), prev: RcuPtr::new(std::ptr::dangling_mut()) }
34 }
35}
36
37/// Returns the container of a given field.
38///
39/// # Safety
40///
41/// The pointer must point to the given field in a valid instance of the container.
42#[macro_export]
43macro_rules! container_of {
44 ($ptr:expr, $container:path, $field:ident) => {{ $ptr.sub_byte_offset::<$container>(memoffset::offset_of!($container, $field)) }};
45}
46
47/// Returns the field of a given container.
48///
49/// # Safety
50///
51/// The pointer must point to a valid instance of the container.
52#[macro_export]
53macro_rules! field_of {
54 ($ptr:expr, $container:path, $field:ident, $field_type:ty) => {{ $ptr.add_byte_offset::<$field_type>(memoffset::offset_of!($container, $field)) }};
55}
56
57#[macro_export]
58macro_rules! rcu_list_adapter {
59 ($node:ty, $link:ident) => {
60 fn to_link(
61 node: fuchsia_rcu::subtle::RcuPtrRef<'_, $node>,
62 ) -> fuchsia_rcu::subtle::RcuPtrRef<'_, Link> {
63 if node.is_null() {
64 return fuchsia_rcu::subtle::RcuPtrRef::null();
65 }
66 // SAFETY: The pointer is valid and points to the given field.
67 unsafe { $crate::field_of!(node, $node, $link, Link) }
68 }
69
70 fn from_link(
71 link: fuchsia_rcu::subtle::RcuPtrRef<'_, Link>,
72 ) -> fuchsia_rcu::subtle::RcuPtrRef<'_, $node> {
73 if link.is_null() {
74 return fuchsia_rcu::subtle::RcuPtrRef::null();
75 }
76 // SAFETY: The pointer is valid and points to the given field.
77 unsafe { $crate::container_of!(link, $node, $link) }
78 }
79 };
80}
81
82pub use container_of;
83pub use field_of;
84pub use rcu_list_adapter;
85
86pub trait RcuListAdapter<T> {
87 /// Returns a pointer to the Link embedded in a Node.
88 fn to_link(node: RcuPtrRef<'_, T>) -> RcuPtrRef<'_, Link>;
89
90 /// Returns a pointer to the Node containing the given Link.
91 fn from_link(link: RcuPtrRef<'_, Link>) -> RcuPtrRef<'_, T>;
92}
93
94#[derive(Debug, RcuDroppable)]
95pub struct RcuIntrusiveList<T, A: RcuListAdapter<T>> {
96 /// The first element of the list, if any.
97 ///
98 /// This field can be used to traverse the list within an RcuReadScope.
99 head: RcuPtr<Link>,
100
101 /// The last element of the list, if any.
102 ///
103 /// This pointer cannot be used without external synchronization.
104 tail: RcuPtr<Link>,
105
106 _marker: std::marker::PhantomData<(T, A)>,
107}
108
109impl<T, A: RcuListAdapter<T>> Default for RcuIntrusiveList<T, A> {
110 fn default() -> Self {
111 Self::new(RcuPtr::null(), RcuPtr::null())
112 }
113}
114
115impl<T, A: RcuListAdapter<T>> RcuIntrusiveList<T, A> {
116 /// Creates a new list with the given head and tail.
117 pub(crate) fn new(head: RcuPtr<Link>, tail: RcuPtr<Link>) -> Self {
118 Self { head, tail, _marker: std::marker::PhantomData }
119 }
120
121 /// Returns whether the list is currently empty without dereferencing any node links.
122 pub fn is_empty(&self, scope: &RcuReadScope) -> bool {
123 self.head.read(scope).is_null()
124 }
125
126 /// Pushes a new element to the front of the list.
127 ///
128 /// Properly initializes `link.prev` to null and `link.next` to the former head (or null
129 /// if the list was empty) before publishing the node to `head`. This ensures that nodes
130 /// previously removed from other lists do not retain poisoned or stale pointers.
131 ///
132 /// Note: Re-inserting a previously removed node overwrites its `link.next` pointer. List
133 /// iteration and re-insertion must be synchronized (e.g., via an external lock or by waiting
134 /// for an RCU grace period after removal); otherwise a concurrent iterator still visiting the
135 /// node may observe wrong results (such as skipping/repeating elements or continuing into the
136 /// destination list).
137 ///
138 /// # Safety
139 ///
140 /// - Requires external synchronization to exclude concurrent writers.
141 /// - `data` must be non-null, properly aligned, and point to a valid, initialized `T` that
142 /// outlives its membership in the list and any subsequent RCU reader grace period.
143 /// - `data` must not currently be attached to any list.
144 pub unsafe fn push_front<'a>(&self, scope: &'a RcuReadScope, data: RcuPtrRef<'a, T>) {
145 let link_ptr = A::to_link(data);
146 let link = link_ptr.as_ref().unwrap();
147 let head_ptr = self.head.read(scope);
148 debug_assert_eq!(
149 link.prev.read(scope).as_ptr(),
150 std::ptr::dangling(),
151 "Attempted to insert a node that is already attached to a list"
152 );
153 link.prev.assign(std::ptr::null_mut());
154 if let Some(head) = head_ptr.as_ref() {
155 link.next.assign_ptr(head_ptr);
156 head.prev.assign_ptr(link_ptr);
157 } else {
158 link.next.assign(std::ptr::null_mut());
159 self.tail.assign_ptr(link_ptr);
160 }
161 self.head.assign_ptr(link_ptr);
162 }
163
164 /// Pushes a new element to the back of the list.
165 ///
166 /// Properly initializes `link.next` to null and `link.prev` to the former tail (or null
167 /// if the list was empty) before publishing the node. This ensures that nodes
168 /// previously removed from other lists do not retain poisoned or stale pointers.
169 ///
170 /// Note: Re-inserting a previously removed node overwrites its `link.next` pointer. List
171 /// iteration and re-insertion must be synchronized (e.g., via an external lock or by waiting
172 /// for an RCU grace period after removal); otherwise a concurrent iterator still visiting the
173 /// node may observe wrong results (such as skipping/repeating elements or continuing into the
174 /// destination list).
175 ///
176 /// # Safety
177 ///
178 /// - Requires external synchronization to exclude concurrent writers.
179 /// - `data` must be non-null, properly aligned, and point to a valid, initialized `T` that
180 /// outlives its membership in the list and any subsequent RCU reader grace period.
181 /// - `data` must not currently be attached to any list.
182 pub unsafe fn push_back<'a>(&self, scope: &'a RcuReadScope, data: RcuPtrRef<'a, T>) {
183 let link_ptr = A::to_link(data);
184 let link = link_ptr.as_ref().unwrap();
185 let tail_ptr = self.tail.read(scope);
186 debug_assert_eq!(
187 link.prev.read(scope).as_ptr(),
188 std::ptr::dangling(),
189 "Attempted to insert a node that is already attached to a list"
190 );
191 link.next.assign(std::ptr::null_mut());
192 if let Some(tail) = tail_ptr.as_ref() {
193 link.prev.assign_ptr(tail_ptr);
194 tail.next.assign_ptr(link_ptr);
195 } else {
196 link.prev.assign(std::ptr::null_mut());
197 self.head.assign_ptr(link_ptr);
198 }
199 self.tail.assign_ptr(link_ptr);
200 }
201
202 /// Appends another list to the end of this list.
203 ///
204 /// # Safety
205 ///
206 /// Requires external synchronization to exclude concurrent writers.
207 pub unsafe fn append(&self, scope: &RcuReadScope, other: Self) {
208 let other_head_ptr = other.head.read(scope);
209 if let Some(other_head) = other_head_ptr.as_ref() {
210 debug_assert!(
211 other_head.prev.read(scope).is_null(),
212 "Head of appended list must have a null prev pointer"
213 );
214 let tail_ptr = self.tail.read(scope);
215 if let Some(tail) = tail_ptr.as_ref() {
216 tail.next.assign_ptr(other_head_ptr);
217 other_head.prev.assign_ptr(tail_ptr);
218 } else {
219 self.head.assign_ptr(other_head_ptr);
220 }
221 let other_tail_ptr = other.tail.read(scope);
222 assert!(!other_tail_ptr.is_null());
223 self.tail.assign_ptr(other_tail_ptr);
224 }
225 other.head.assign(std::ptr::null_mut());
226 other.tail.assign(std::ptr::null_mut());
227 }
228
229 /// Removes the given node from the list.
230 ///
231 /// Returns the link of the next node in the list, if any.
232 ///
233 /// Concurrent readers may continue to see this entry in the list until the RCU state machine
234 /// has made sufficient progress to ensure that no concurrent readers are holding read guards.
235 /// If the removed `node` is re-inserted into a list, list iteration and re-insertion must be
236 /// synchronized; otherwise concurrent iterators still visiting `node` may observe wrong
237 /// results.
238 ///
239 /// # Safety
240 ///
241 /// - Requires external synchronization to exclude concurrent writers.
242 /// - `node` must be non-null, properly aligned, and point to a valid, initialized `T`
243 /// currently attached to `self`.
244 /// - The removed `node` must not be dropped while concurrent RCU readers may still be visiting
245 /// it (e.g., wait for an RCU grace period before dropping).
246 pub unsafe fn remove<'a>(
247 &self,
248 scope: &'a RcuReadScope,
249 node: RcuPtrRef<'a, T>,
250 ) -> RcuPtrRef<'a, Link> {
251 let link_ptr = A::to_link(node);
252 let link = link_ptr.as_ref().unwrap();
253
254 let prev = link.prev.read(scope);
255 let next = link.next.read(scope);
256
257 debug_assert_ne!(
258 prev.as_ptr(),
259 std::ptr::dangling(),
260 "Attempted to remove a node whose Link::prev is poisoned (unattached or already removed)"
261 );
262 debug_assert!(
263 prev.as_ref().is_some_and(|p| p.next.read(scope).as_ptr() == link_ptr.as_ptr())
264 || (prev.is_null() && self.head.read(scope).as_ptr() == link_ptr.as_ptr()),
265 "Attempted to remove a node not attached to this RcuIntrusiveList"
266 );
267 debug_assert!(
268 next.as_ref().is_some_and(|n| n.prev.read(scope).as_ptr() == link_ptr.as_ptr())
269 || (next.is_null() && self.tail.read(scope).as_ptr() == link_ptr.as_ptr()),
270 "Attempted to remove a node not attached to this RcuIntrusiveList"
271 );
272
273 if let Some(next) = next.as_ref() {
274 next.prev.assign_ptr(prev);
275 } else {
276 self.tail.assign_ptr(prev);
277 }
278 if let Some(prev) = prev.as_ref() {
279 prev.next.assign_ptr(next);
280 } else {
281 self.head.assign_ptr(next);
282 }
283
284 // Other readers may continue to see this entry in the list and use the `next` pointer,
285 // but they should not read the `prev` pointer anymore.
286 link.prev.poison();
287
288 next
289 }
290
291 /// Splits the list into two lists at the given position.
292 ///
293 /// If the given position is past the end of the list, returns an empty list.
294 ///
295 /// # Safety
296 ///
297 /// Requires external synchronization to exclude concurrent writers.
298 pub unsafe fn split_off(&self, scope: &RcuReadScope, pos: usize) -> Self {
299 // If splitting at the front, return the entire list and clear the list.
300 if pos == 0 {
301 let head = RcuPtr::new(self.head.replace(std::ptr::null_mut()));
302 let tail = RcuPtr::new(self.tail.replace(std::ptr::null_mut()));
303 return Self::new(head, tail);
304 }
305 let mut i = 1;
306 let mut prev_ptr = self.head.read(scope);
307 while let Some(prev) = prev_ptr.as_ref() {
308 if i == pos {
309 let head_ptr = prev.next.swap(scope, std::ptr::null_mut());
310 let Some(head) = head_ptr.as_ref() else {
311 // There are no elements after the split point, so return an empty list.
312 break;
313 };
314 let tail = self.tail.read(scope);
315 self.tail.assign_ptr(prev_ptr);
316 head.prev.assign(std::ptr::null_mut());
317 return Self::new(
318 RcuPtr::new(head_ptr.as_mut_ptr()),
319 RcuPtr::new(tail.as_mut_ptr()),
320 );
321 }
322 prev_ptr = prev.next.read(scope);
323 i += 1;
324 }
325 // End of list reached, return an empty list.
326 Self::default()
327 }
328
329 /// Updates the list with the contents of another list.
330 ///
331 /// Any elements previously in `self` are replaced in place without poisoning their `Link::prev`
332 /// pointers; call `clear` first if those elements need to be re-attached to a list later.
333 ///
334 /// # Safety
335 ///
336 /// Requires external synchronization to exclude concurrent writers.
337 pub unsafe fn update(&self, scope: &RcuReadScope, other: Self) {
338 self.head.assign_ptr(other.head.read(scope));
339 self.tail.assign_ptr(other.tail.read(scope));
340 }
341
342 /// Removes all elements from the list.
343 ///
344 /// The callback is called for each element in the list. The caller is responsible for cleaning
345 /// up the removed elements.
346 ///
347 /// Concurrent readers may continue to see the old value of the list until the RCU state machine
348 /// has made sufficient progress to ensure that no concurrent readers are holding read guards.
349 /// If removed elements are re-inserted into a list, list iteration and re-insertion must be
350 /// synchronized; otherwise concurrent iterators still visiting those elements may observe
351 /// wrong results.
352 ///
353 /// # Safety
354 ///
355 /// - Requires external synchronization to exclude concurrent writers.
356 /// - Removed elements must not be dropped while concurrent RCU readers may still be visiting
357 /// them (e.g., wait for an RCU grace period before dropping).
358 pub unsafe fn clear<'a>(&self, scope: &'a RcuReadScope, callback: impl Fn(RcuPtrRef<'a, T>))
359 where
360 T: 'static,
361 {
362 let mut current = self.head.read(scope);
363
364 self.head.assign(std::ptr::null_mut());
365 self.tail.assign(std::ptr::null_mut());
366
367 while let Some(link) = current.as_ref() {
368 let next = link.next.read(scope);
369
370 // Other readers may continue to see this entry in the list and use the `next` pointer,
371 // but they should not read the `prev` pointer anymore.
372 link.prev.poison();
373 callback(A::from_link(current));
374 current = next;
375 }
376 }
377
378 /// Returns a cursor that can be used to traverse and modify the list.
379 ///
380 /// Concurrent readers may continue to see the old value of the list until the RCU state machine
381 /// has made sufficient progress to ensure that no concurrent readers are holding read guards.
382 pub fn cursor<'a>(&'a self, scope: &'a RcuReadScope) -> RcuIntrusiveListCursor<'a, T, A> {
383 let current = self.head.read(scope);
384 RcuIntrusiveListCursor { scope, list: self, current }
385 }
386
387 /// Returns an iterator over the elements in the list.
388 pub fn iter<'a>(&self, scope: &'a RcuReadScope) -> impl Iterator<Item = &'a T>
389 where
390 T: 'static,
391 {
392 let next = self.head.read(&scope);
393 RcuIntrusiveListIter::<T, A> { scope, next, _marker: std::marker::PhantomData }
394 }
395}
396
397/// A cursor for traversing and modifying an `RcuList`.
398///
399/// See `RcuList::cursor` for more information.
400pub struct RcuIntrusiveListCursor<'a, T, A: RcuListAdapter<T>> {
401 scope: &'a RcuReadScope,
402 list: &'a RcuIntrusiveList<T, A>,
403 current: RcuPtrRef<'a, Link>,
404}
405
406impl<'a, T, A: RcuListAdapter<T>> RcuIntrusiveListCursor<'a, T, A> {
407 /// Returns the element at the current cursor position.
408 pub fn current(&self) -> Option<&'a T> {
409 let node = A::from_link(self.current);
410 node.as_ref()
411 }
412
413 /// Advances the cursor to the next element in the list.
414 pub fn advance(&mut self) {
415 if let Some(link) = self.current.as_ref() {
416 self.current = link.next.read(&self.scope);
417 }
418 }
419
420 /// Removes the element at the current cursor position.
421 ///
422 /// After calling `remove`, the cursor will be positioned at the next element in the list.
423 ///
424 /// Returns a pointer to the removed element. The caller is responsible for cleaning up the
425 /// removed element.
426 ///
427 /// Concurrent readers may continue to see this entry in the list until the RCU state machine
428 /// has made sufficient progress to ensure that no concurrent readers are holding read guards.
429 /// If the removed element is re-inserted into a list, list iteration and re-insertion must be
430 /// synchronized; otherwise concurrent iterators still visiting the element may observe wrong
431 /// results.
432 ///
433 /// # Safety
434 ///
435 /// - Requires external synchronization to exclude concurrent writers.
436 /// - The removed element must not be dropped while concurrent RCU readers may still be
437 /// visiting it (e.g., wait for an RCU grace period before dropping).
438 pub unsafe fn remove(&mut self) -> RcuPtrRef<'a, T> {
439 if self.current.is_null() {
440 return RcuPtrRef::null();
441 }
442 let removed_node = A::from_link(self.current);
443 // SAFETY: The caller promises to exclude concurrent writers, and `self.current` is a
444 // valid non-null node currently attached to `self.list`.
445 unsafe {
446 self.current = self.list.remove(self.scope, removed_node);
447 }
448 removed_node
449 }
450}
451
452struct RcuIntrusiveListIter<'a, T, A: RcuListAdapter<T>> {
453 scope: &'a RcuReadScope,
454 next: RcuPtrRef<'a, Link>,
455 _marker: std::marker::PhantomData<(T, A)>,
456}
457
458impl<'a, T: 'static, A: RcuListAdapter<T>> Iterator for RcuIntrusiveListIter<'a, T, A> {
459 type Item = &'a T;
460
461 fn next(&mut self) -> Option<Self::Item> {
462 if let Some(link) = self.next.as_ref() {
463 let current = self.next;
464 self.next = link.next.read(&self.scope);
465 Some(A::from_link(current).as_ref().unwrap())
466 } else {
467 None
468 }
469 }
470}