Skip to main content

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}