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}