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}