Skip to main content

fuchsia_rcu/
rcu_option_box.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
5use crate::rcu_ptr::{RcuPtr, RcuReadGuard};
6use crate::rcu_read_scope::RcuReadScope;
7use crate::state_machine::rcu_drop;
8
9use crate::rcu_droppable::RcuDroppable;
10
11/// An RCU (Read-Copy-Update) wrapper around `Option<Box>`.
12///
13/// The Box can be dereferenced from multiple threads concurrently without blocking.
14/// When the Box is replaced, reads may continue to see the old Box for some period of time.
15#[derive(Debug)]
16pub struct RcuOptionBox<T: RcuDroppable + Sync> {
17    ptr: RcuPtr<T>,
18}
19
20impl<T: RcuDroppable + Sync> RcuOptionBox<T> {
21    /// Create a new RCU Cell from a value.
22    pub fn new(data: Option<T>) -> Self {
23        Self::from(data.map(|data| Box::new(data)))
24    }
25
26    /// Read the value of the wrapped Box, if present.
27    ///
28    /// The object referenced by the Box will remain valid until the `RcuReadGuard` is dropped.
29    /// However, another thread running concurrently might see a different object.
30    pub fn read(&self) -> Option<RcuReadGuard<T>> {
31        self.ptr.maybe_get()
32    }
33
34    /// Returns a reference to the value of the wrapped Box, if present.
35    ///
36    /// The object referenced by the Box will remain valid until the `RcuReadScope` is dropped.
37    /// However, another thread running concurrently might see a different object.
38    pub fn as_ref<'a>(&self, scope: &'a RcuReadScope) -> Option<&'a T> {
39        self.ptr.read(scope).as_ref()
40    }
41
42    /// Returns `true` if the RCU wrapper currently contains a value.
43    pub fn is_some(&self, scope: &RcuReadScope) -> bool {
44        self.as_ref(scope).is_some()
45    }
46
47    /// Returns `true` if the RCU wrapper does not contain a value.
48    pub fn is_none(&self, scope: &RcuReadScope) -> bool {
49        self.as_ref(scope).is_none()
50    }
51
52    /// Write a new value to the RCU wrapper.
53    ///
54    /// Concurrent readers may continue to see the old value until the RCU state machine has
55    /// made sufficient progress to ensure that no concurrent readers are holding read guards.
56    pub fn update(&self, data: Option<T>) {
57        let ptr = data.map(|data| Box::into_raw(Box::new(data))).unwrap_or(std::ptr::null_mut());
58        // SAFETY: We can pass `Box::into_raw` to `Self::replace`.
59        unsafe { self.replace(ptr) };
60    }
61
62    /// Replace the pointer in the RCU wrapper with a new pointer.
63    ///
64    /// # Safety
65    ///
66    /// The pointer must have been created by `Box::into_raw` or from `std::ptr::null_mut`.
67    unsafe fn replace(&self, ptr: *mut T) {
68        let old_ptr = self.ptr.replace(ptr);
69        if !old_ptr.is_null() {
70            // SAFETY: `old_ptr` was created by `Box::into_raw`.
71            let object = unsafe { Box::from_raw(old_ptr) };
72            rcu_drop(object);
73        }
74    }
75}
76
77impl<T: Clone + RcuDroppable + Sync> RcuOptionBox<T> {
78    /// Returns a clone of the value of the wrapped Box, if present.
79    ///
80    /// The clone is detached from any RCU read scope.
81    pub fn cloned(&self) -> Option<T> {
82        self.as_ref(&RcuReadScope::new()).cloned()
83    }
84}
85
86impl<T: RcuDroppable + Sync> Drop for RcuOptionBox<T> {
87    fn drop(&mut self) {
88        // SAFETY: We can pass `std::ptr::null_mut` to `Self::replace`.
89        unsafe { self.replace(std::ptr::null_mut()) };
90    }
91}
92
93impl<T: RcuDroppable + Sync> Default for RcuOptionBox<T> {
94    fn default() -> Self {
95        Self::new(None)
96    }
97}
98
99impl<T: Clone + RcuDroppable + Sync> Clone for RcuOptionBox<T> {
100    fn clone(&self) -> Self {
101        let value = self.read();
102        Self::new(value.map(|value| value.clone()))
103    }
104}
105
106impl<T: RcuDroppable + Sync> From<Option<Box<T>>> for RcuOptionBox<T> {
107    fn from(value: Option<Box<T>>) -> Self {
108        let ptr = value.map(|value| Box::into_raw(value)).unwrap_or(std::ptr::null_mut());
109        Self { ptr: RcuPtr::new(ptr) }
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116    use crate::state_machine::rcu_run_callbacks;
117    use std::ops::Deref;
118
119    #[test]
120    fn test_rcu_option_cell() {
121        let scope = RcuReadScope::new();
122        let value = RcuOptionBox::new(Some(42));
123        assert_eq!(value.read().unwrap().deref(), &42);
124        assert!(value.is_some(&scope));
125        assert!(!value.is_none(&scope));
126
127        let value = RcuOptionBox::<i32>::new(None);
128        assert!(value.read().is_none());
129        assert!(value.is_none(&scope));
130        assert!(!value.is_some(&scope));
131    }
132
133    #[test]
134    fn test_rcu_option_cell_set_deferred() {
135        let value = RcuOptionBox::new(Some(42));
136        value.update(Some(43));
137        assert_eq!(value.read().unwrap().deref(), &43);
138
139        value.update(None);
140        assert!(value.read().is_none());
141
142        rcu_run_callbacks();
143        assert!(value.read().is_none());
144    }
145
146    #[test]
147    fn test_rcu_option_cell_drop() {
148        let value = RcuOptionBox::new(Some(42));
149        drop(value);
150    }
151}