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    /// Current ref count. Only to be used for debugging purposes.
72    pub fn ref_count_debug(&self) -> i32 {
73        self.ref_count.load(Ordering::Relaxed)
74    }
75}
76
77/// Trait to be implemented by types that contain a `RefCounted` field.
78///
79/// Used to locate the `RefCounted` field within a type.
80pub trait HasRefCount {
81    /// Returns a reference to the contained `RefCounted` field.
82    fn ref_count(&self) -> &RefCounted;
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn test_new() {
91        let rc = RefCounted::new();
92        #[cfg(debug_assertions)]
93        assert_eq!(rc.ref_count_debug(), PRE_ADOPT_SENTINEL);
94        #[cfg(not(debug_assertions))]
95        assert_eq!(rc.ref_count_debug(), 1);
96    }
97
98    #[test]
99    fn test_add_ref() {
100        let rc = RefCounted::new();
101        rc.adopt();
102        rc.add_ref();
103        assert_eq!(rc.ref_count_debug(), 2);
104    }
105
106    #[test]
107    fn test_release() {
108        let rc = RefCounted::new();
109        rc.adopt();
110        assert!(rc.release()); // returns true, count becomes 0
111        assert_eq!(rc.ref_count_debug(), 0);
112    }
113
114    #[test]
115    #[should_panic(expected = "AddRef on un-adopted or destroyed object")]
116    fn test_add_ref_panic() {
117        let rc = RefCounted::new();
118        rc.adopt();
119        assert!(rc.release()); // count becomes 0
120        rc.add_ref(); // Should panic!
121    }
122
123    #[test]
124    #[should_panic(expected = "Release on un-adopted or destroyed object")]
125    fn test_release_panic() {
126        let rc = RefCounted::new();
127        rc.adopt();
128        assert!(rc.release()); // count becomes 0
129        let _ = rc.release(); // Should panic!
130    }
131}