Skip to main content

fuchsia_rcu_collections/
rcu_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, rcu_drop};
9
10use crate::rcu_intrusive_list::{Link, RcuIntrusiveList, RcuIntrusiveListCursor, RcuListAdapter};
11
12/// An `RcuList` is a doubly-linked list that supports concurrent access via
13/// read-copy-update (RCU) synchronization.
14///
15/// An `RcuList` can be safely read by multiple readers, even while a writer
16/// is modifying the list. To read from the list, you will need to enter an
17/// `RcuReadScope`.
18///
19/// To modify the list, you will need to use some external synchronization,
20/// such as a `Mutex`, to exclude concurrent writers.
21#[derive(Debug)]
22pub struct RcuList<T: RcuDroppable + Sync, A: RcuListAdapter<T>> {
23    list: RcuIntrusiveList<T, A>,
24}
25
26// SAFETY: RcuList drops all elements through its intrusive list nodes, which are of type T
27// (implementing RcuDroppable).
28unsafe impl<T: RcuDroppable + Sync, A: RcuListAdapter<T> + Send + Sync + 'static> RcuDroppable
29    for RcuList<T, A>
30{
31}
32
33impl<T: RcuDroppable + Sync, A: RcuListAdapter<T>> Default for RcuList<T, A> {
34    fn default() -> Self {
35        Self { list: RcuIntrusiveList::default() }
36    }
37}
38
39impl<T: RcuDroppable + Sync, A: RcuListAdapter<T>> RcuList<T, A> {
40    /// Creates a new list with the given head and tail.
41    pub fn new(head: RcuPtr<Link>, tail: RcuPtr<Link>) -> Self {
42        Self { list: RcuIntrusiveList::new(head, tail) }
43    }
44
45    /// Pushes a new element to the front of the list.
46    ///
47    /// # Safety
48    ///
49    /// Requires external synchronization to exclude concurrent writers.
50    pub unsafe fn push_front<'a>(&self, scope: &'a RcuReadScope, data: T) -> RcuPtrRef<'a, T> {
51        let node = alloc(scope, data);
52        // SAFETY: Our caller promises to exclude concurrent writers.
53        unsafe {
54            self.list.push_front(scope, node);
55        }
56        node
57    }
58
59    /// Pushes a new element to the back of the list.
60    ///
61    /// # Safety
62    ///
63    /// Requires external synchronization to exclude concurrent writers.
64    pub unsafe fn push_back<'a>(&self, scope: &'a RcuReadScope, data: T) -> RcuPtrRef<'a, T> {
65        let node = alloc(scope, data);
66        // SAFETY: Our caller promises to exclude concurrent writers.
67        unsafe {
68            self.list.push_back(scope, node);
69        }
70        node
71    }
72
73    /// Appends another list to the end of this list.
74    ///
75    /// # Safety
76    ///
77    /// Requires external synchronization to exclude concurrent writers.
78    pub unsafe fn append(&self, scope: &RcuReadScope, other: Self) {
79        // SAFETY: Our caller promises to exclude concurrent writers.
80        unsafe {
81            let items = other.list.split_off(scope, 0);
82            self.list.append(scope, items);
83        }
84    }
85
86    /// Splits the list into two lists at the given position.
87    ///
88    /// If the given position is past the end of the list, returns an empty list.
89    ///
90    /// # Safety
91    ///
92    /// Requires external synchronization to exclude concurrent writers.
93    pub unsafe fn split_off(&self, scope: &RcuReadScope, pos: usize) -> Self {
94        // SAFETY: Our caller promises to exclude concurrent writers.
95        Self { list: unsafe { self.list.split_off(scope, pos) } }
96    }
97
98    /// Removes all elements from the list.
99    ///
100    /// Concurrent readers may continue to see the old value of the list until the RCU state machine
101    /// has made sufficient progress to ensure that no concurrent readers are holding read guards.
102    ///
103    /// # Safety
104    ///
105    /// Requires external synchronization to exclude concurrent writers.
106    pub unsafe fn clear(&self) {
107        let scope = RcuReadScope::new();
108        // SAFETY: Our caller promises to exclude concurrent writers.
109        unsafe { self.list.clear(&scope, deferred_dealloc) };
110    }
111
112    #[cfg(test)]
113    pub(crate) fn is_empty(&self) -> bool {
114        let scope = RcuReadScope::new();
115        self.list.is_empty(&scope)
116    }
117
118    /// Returns a cursor that can be used to traverse and modify the list.
119    ///
120    /// Concurrent readers may continue to see the old value of the list until the RCU state machine
121    /// has made sufficient progress to ensure that no concurrent readers are holding read guards.
122    pub fn cursor<'a>(&'a self, scope: &'a RcuReadScope) -> RcuListCursor<'a, T, A> {
123        RcuListCursor { cursor: self.list.cursor(scope) }
124    }
125
126    /// Returns an iterator over the elements in the list.
127    pub fn iter<'a>(&self, scope: &'a RcuReadScope) -> impl Iterator<Item = &'a T> {
128        self.list.iter(scope)
129    }
130}
131
132/// Allocates a new node.
133///
134/// The node must be deallocated using `deferred_dealloc`.
135fn alloc<T>(scope: &RcuReadScope, data: T) -> RcuPtrRef<'_, T> {
136    let ptr = Box::into_raw(Box::new(data));
137    // SAFETY: All nodes must be deallocated using `deferred_dealloc`, which defers their
138    // deallocation until all in-flight read operations have completed.
139    unsafe { RcuPtrRef::new(scope, ptr) }
140}
141
142/// Deallocates a node once all in-flight read operations have completed.
143///
144/// The node must have been allocated using `alloc`.
145fn deferred_dealloc<T>(node: RcuPtrRef<'_, T>)
146where
147    T: RcuDroppable + Sync,
148{
149    // SAFETY: The node was allocated using `alloc`.
150    let value = unsafe { Box::from_raw(node.as_mut_ptr()) };
151    rcu_drop(value);
152}
153
154pub struct RcuListCursor<'a, T: RcuDroppable + Sync, A: RcuListAdapter<T>> {
155    cursor: RcuIntrusiveListCursor<'a, T, A>,
156}
157
158impl<'a, T: RcuDroppable + Sync, A: RcuListAdapter<T>> RcuListCursor<'a, T, A> {
159    /// Returns the element at the current cursor position.
160    pub fn current(&self) -> Option<&T> {
161        self.cursor.current()
162    }
163
164    /// Advances the cursor to the next element in the list.
165    pub fn advance(&mut self) {
166        self.cursor.advance();
167    }
168
169    /// Removes the element at the current cursor position.
170    ///
171    /// After calling `remove`, the cursor will be positioned at the next element in the list.
172    ///
173    /// Concurrent readers may continue to see this entry in the list until the RCU state machine
174    /// has made sufficient progress to ensure that no concurrent readers are holding read guards.
175    ///
176    /// # Safety
177    ///
178    /// Requires external synchronization to exclude concurrent writers.
179    pub unsafe fn remove(&mut self) -> RcuPtrRef<'a, T> {
180        let removed = unsafe { self.cursor.remove() };
181        deferred_dealloc(removed);
182        removed
183    }
184}
185
186impl<T: RcuDroppable + Sync, A: RcuListAdapter<T>> Drop for RcuList<T, A> {
187    fn drop(&mut self) {
188        // SAFETY: The list is being dropped, so there are no concurrent readers.
189        unsafe { self.clear() };
190    }
191}