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