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