include/boost/corosio/detail/object_ref.hpp

100.0% Lines (44 / 44) 100.0% Functions (15 / 15)
object_ref.hpp
f(x) Functions (15)
Line TLA Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 // Copyright (c) 2026 Steve Gerbino
4 //
5 // Distributed under the Boost Software License, Version 1.0. (See accompanying
6 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7 //
8 // Official repository: https://github.com/cppalliance/corosio
9 //
10
11 #ifndef BOOST_COROSIO_DETAIL_OBJECT_REF_HPP
12 #define BOOST_COROSIO_DETAIL_OBJECT_REF_HPP
13
14 #include <boost/corosio/detail/platform.hpp>
15 #include <boost/corosio/io/io_object.hpp>
16 #include <atomic>
17 #include <cstddef>
18 #include <limits>
19 #include <utility>
20
21 namespace boost::corosio::detail {
22
23 /// Add one reference to `impl`. `impl` must be non-null.
24 inline void
25 143295x acquire(io_object::implementation* impl) noexcept
26 {
27 143295x impl->refs_.fetch_add(1, std::memory_order_relaxed);
28 143295x }
29
30 /** The count an `object_pool` stamps on an impl parked for reuse.
31
32 It holds no reference; see `object_pool`.
33 */
34 inline constexpr std::size_t pooled_refs =
35 (std::numeric_limits<std::size_t>::max)();
36
37 /** Add one reference to `impl` unless it is retiring or parked.
38
39 A count at zero means `retire()` is running or about to, and a
40 count of `pooled_refs` means the impl waits in its pool for a new
41 owner; neither may be revived.
42
43 @return True if a reference was added.
44 */
45 inline bool
46 9996x try_acquire(io_object::implementation* impl) noexcept
47 {
48 9996x auto n = impl->refs_.load(std::memory_order_relaxed);
49 9999x while (n != 0 && n != pooled_refs)
50 {
51 19990x if (impl->refs_.compare_exchange_weak(
52 n, n + 1, std::memory_order_relaxed))
53 9992x return true;
54 }
55 4x return false;
56 }
57
58 /** Drop one reference; runs `retire()` at zero. `impl` must be
59 non-null.
60
61 Uses release on the decrement and an acquire fence before the
62 zero-action so writes made while holding a reference are visible
63 to the recycler. TSan cannot model a standalone fence, so under
64 it the decrement itself is acq_rel instead -- strictly stronger,
65 and TSan's happens-before tracking models the exchange directly.
66 */
67 inline void
68 189381x release(io_object::implementation* impl) noexcept
69 {
70 #if BOOST_COROSIO_TSAN
71 if (impl->refs_.fetch_sub(1, std::memory_order_acq_rel) == 1)
72 impl->retire();
73 #else
74 378762x if (impl->refs_.fetch_sub(1, std::memory_order_release) == 1)
75 {
76 std::atomic_thread_fence(std::memory_order_acquire);
77 36094x impl->retire();
78 }
79 #endif
80 189381x }
81
82 /** Owning intrusive reference to an I/O implementation.
83
84 Replaces `std::shared_ptr<void>` op keepalives: copy acquires,
85 move steals, destruction releases. Kept deliberately minimal —
86 one pointer, half the size of a shared_ptr, and no control block.
87 */
88 class object_ref
89 {
90 io_object::implementation* p_ = nullptr;
91
92 public:
93 /// Destroy the object_ref, releasing the held reference, if any.
94 595448x ~object_ref() { reset(); }
95
96 /// Construct an object_ref holding no reference.
97 90354x object_ref() noexcept = default;
98
99 /// Construct an object_ref that acquires a new reference to `p`.
100 140069x explicit object_ref(io_object::implementation* p) noexcept : p_(p)
101 {
102 140069x if (p_)
103 140069x acquire(p_);
104 140069x }
105
106 /** Create a reference to `p` unless its count already reached zero.
107
108 The way to take a reference from a raw pointer: a context that
109 holds none may find the count at zero, and must not revive it.
110
111 @param p The implementation to reference.
112
113 @return A reference to `p`, or an empty `object_ref` if `p` is
114 retiring.
115 */
116 795x static object_ref try_from(io_object::implementation* p) noexcept
117 {
118 795x object_ref r;
119 795x if (try_acquire(p))
120 793x r.p_ = p;
121 795x return r;
122 }
123
124 /// Construct a copy, acquiring another reference to `other`'s target.
125 3x object_ref(object_ref const& other) noexcept : p_(other.p_)
126 {
127 3x if (p_)
128 3x acquire(p_);
129 3x }
130
131 /// Construct by transferring the reference out of `other`.
132 310011x object_ref(object_ref&& other) noexcept
133 310011x : p_(std::exchange(other.p_, nullptr))
134 {
135 310011x }
136
137 /// Release the held reference, if any, and acquire `other`'s target.
138 2x object_ref& operator=(object_ref const& other) noexcept
139 {
140 2x object_ref(other).swap(*this);
141 2x return *this;
142 }
143
144 /// Release the held reference, if any, and take ownership of `other`'s.
145 183905x object_ref& operator=(object_ref&& other) noexcept
146 {
147 183905x object_ref(std::move(other)).swap(*this);
148 183905x return *this;
149 }
150
151 /// Release the held reference, if any.
152 1092312x void reset() noexcept
153 {
154 1092312x if (p_)
155 140865x release(std::exchange(p_, nullptr));
156 1092312x }
157
158 /// Return the held implementation pointer.
159 4x io_object::implementation* get() const noexcept { return p_; }
160
161 /// Return true if a reference is held.
162 83210x explicit operator bool() const noexcept { return p_ != nullptr; }
163
164 /// Exchange the implementation pointers held by `*this` and `other`.
165 183907x void swap(object_ref& other) noexcept { std::swap(p_, other.p_); }
166 };
167
168 } // namespace boost::corosio::detail
169
170 #endif
171