Skip to main content

fbl/
ref_counted.rs

1// Copyright 2026 The Fuchsia Authors
2//
3// Use of this source code is governed by a MIT-style
4// license that can be found in the LICENSE file or at
5// https://opensource.org/licenses/MIT
6
7use core::sync::atomic::{AtomicI32, Ordering};
8
9/// Support for intrusive atomic reference counting.
10///
11/// This supports intrusive atomic reference counting with adoption. This means
12/// that a new object starts life at a reference count of 1 and has to be adopted
13/// by a type (such as a `fbl::RefPtr`) that begins manipulation of the reference
14/// count. If the reference count ever reaches zero, the object's lifetime is
15/// over and it should be destroyed (`Release()` returns true if this is the case).
16#[repr(C)]
17#[derive(Debug)]
18pub struct RefCounted {
19    ref_count: AtomicI32,
20}
21
22#[cfg(debug_assertions)]
23const PRE_ADOPT_SENTINEL: i32 = 0xC0000000u32 as i32;
24
25impl RefCounted {
26    /// Create a new RefCounted.
27    ///
28    /// In debug builds, the initial count is set to a sentinel value to detect
29    /// use before adoption. In release builds, it starts at 1.
30    pub const fn new() -> Self {
31        #[cfg(debug_assertions)]
32        let initial_count = PRE_ADOPT_SENTINEL;
33        #[cfg(not(debug_assertions))]
34        let initial_count = 1;
35
36        RefCounted { ref_count: AtomicI32::new(initial_count) }
37    }
38
39    /// Transition the object from unadopted to adopted state.
40    ///
41    /// # Safety
42    ///
43    /// The object has not been adopted: its reference count is still in the state
44    /// `RefCounted::new` left it in. Nothing else accesses the reference count during the call.
45    pub unsafe fn adopt(&self) {
46        #[cfg(debug_assertions)]
47        {
48            let expected = PRE_ADOPT_SENTINEL;
49            let res =
50                self.ref_count.compare_exchange(expected, 1, Ordering::AcqRel, Ordering::Acquire);
51            assert!(res.is_ok(), "Double adopt or adopt on invalid object");
52        }
53        #[cfg(not(debug_assertions))]
54        {
55            self.ref_count.store(1, Ordering::Release);
56        }
57    }
58
59    /// Increment the reference count.
60    pub(crate) fn add_ref(&self) {
61        let rc = self.ref_count.fetch_add(1, Ordering::Relaxed);
62        assert!(rc >= 1, "AddRef on un-adopted or destroyed object");
63    }
64
65    /// Decrement the reference count. Returns true if the object should be destroyed.
66    #[must_use = "Release must be checked to determine if the object should be deleted"]
67    pub(crate) fn release(&self) -> bool {
68        let rc = self.ref_count.fetch_sub(1, Ordering::Release);
69        assert!(rc >= 1, "Release on un-adopted or destroyed object");
70        if rc == 1 {
71            core::sync::atomic::fence(Ordering::Acquire);
72            return true;
73        }
74        false
75    }
76
77    /// This method must only be called from make_ref_ptr_upgrade_from_raw.  See its
78    /// comments for details in the proper use of this method. The actual job of
79    /// this function is to atomically increment the refcount if the refcount is
80    /// greater than zero.
81    ///
82    /// This method returns false if the object was found with an invalid
83    /// refcount (refcount was <= 0), and true if the refcount was not zero and
84    /// it was incremented.
85    ///
86    /// The procedure used is the while-CAS loop with the advantage that
87    /// compare_exchange on failure updates |old| on failure (to exchange) so the
88    /// loop does not have to do a separate load.
89    #[must_use = "Result must be checked to determine if the object was adopted"]
90    pub(crate) fn add_ref_maybe_in_destructor(&self) -> bool {
91        let mut old = self.ref_count.load(Ordering::Acquire);
92        loop {
93            if old <= 0 {
94                return false;
95            }
96            match self.ref_count.compare_exchange_weak(
97                old,
98                old + 1,
99                Ordering::AcqRel,
100                Ordering::Acquire,
101            ) {
102                Ok(_) => {
103                    return true;
104                }
105                Err(actual) => old = actual,
106            }
107        }
108    }
109
110    /// Current ref count. Only to be used for debugging purposes.
111    pub fn ref_count_debug(&self) -> i32 {
112        self.ref_count.load(Ordering::Relaxed)
113    }
114}
115
116impl Default for RefCounted {
117    fn default() -> Self {
118        Self::new()
119    }
120}
121
122/// Trait to be implemented by types that contain a `RefCounted` field.
123///
124/// Used to locate the `RefCounted` field within a type.
125pub trait HasRefCount {
126    /// Returns a reference to the contained `RefCounted` field.
127    fn ref_count(&self) -> &RefCounted;
128}
129
130/// Trait for an object to mark itself as allowing the `make_ref_ptr_upgrade_from_raw` operation,
131/// which can be used to give weak-pointer like behavior for very specific use cases.
132///
133/// Although an object does not need to do anything additional to support this trait, it requires an
134/// explicit implementation as this is only intended to resolve certain specific circular
135/// dependencies.
136pub trait HasRefCountUpgradeable: HasRefCount {}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    #[test]
143    fn test_new() {
144        let rc = RefCounted::new();
145        #[cfg(debug_assertions)]
146        assert_eq!(rc.ref_count_debug(), PRE_ADOPT_SENTINEL);
147        #[cfg(not(debug_assertions))]
148        assert_eq!(rc.ref_count_debug(), 1);
149    }
150
151    #[test]
152    fn test_add_ref() {
153        let rc = RefCounted::new();
154        // SAFETY: No `RefPtr` to `rc` exists.
155        unsafe { rc.adopt() };
156        rc.add_ref();
157        assert_eq!(rc.ref_count_debug(), 2);
158    }
159
160    #[test]
161    fn test_release() {
162        let rc = RefCounted::new();
163        // SAFETY: No `RefPtr` to `rc` exists.
164        unsafe { rc.adopt() };
165        assert!(rc.release()); // returns true, count becomes 0
166        assert_eq!(rc.ref_count_debug(), 0);
167    }
168
169    #[test]
170    #[should_panic(expected = "AddRef on un-adopted or destroyed object")]
171    fn test_add_ref_panic() {
172        let rc = RefCounted::new();
173        // SAFETY: No `RefPtr` to `rc` exists.
174        unsafe { rc.adopt() };
175        assert!(rc.release()); // count becomes 0
176        rc.add_ref(); // Should panic!
177    }
178
179    #[test]
180    #[should_panic(expected = "Release on un-adopted or destroyed object")]
181    fn test_release_panic() {
182        let rc = RefCounted::new();
183        // SAFETY: No `RefPtr` to `rc` exists.
184        unsafe { rc.adopt() };
185        assert!(rc.release()); // count becomes 0
186        let _ = rc.release(); // Should panic!
187    }
188}