include/boost/corosio/io/io_object.hpp
100.0% Lines (70 / 70)
68.2% Branches (15 / 22)
100.0% Functions (22 / 22, 1 excl)
Functions (23)
Function
Calls
Lines
Branches
Blocks
boost::corosio::io_object::implementation::implementation()
:62
18892x
100.0%
–
100.0%
boost::corosio::io_object::implementation::~implementation()
:65
18885x
100.0%
–
100.0%
boost::corosio::io_object::io_service::io_service()
:90
5961x
100.0%
–
100.0%
boost::corosio::io_object::io_service::~io_service()
:93
5961x
100.0%
–
100.0%
boost::corosio::io_object::io_service::close(boost::corosio::io_object::handle&)
:120
18952x
25.0%
–
100.0%
<unknown function 128>
:128
–
–
–
–
boost::corosio::io_object::handle::~handle()
:134
250904x
100.0%
100.0%
100.0%
boost::corosio::io_object::handle::handle()
:144
44x
100.0%
–
100.0%
boost::corosio::io_object::handle::handle(boost::capy::execution_context&, boost::corosio::io_object::io_service&, boost::corosio::io_object::implementation*)
:155
32x
100.0%
–
100.0%
boost::corosio::io_object::handle::handle(boost::capy::execution_context&, boost::corosio::io_object::io_service&)
:166
106977x
100.0%
–
100.0%
boost::corosio::io_object::handle::handle(boost::corosio::io_object::handle&&)
:174
143866x
100.0%
–
100.0%
boost::corosio::io_object::handle::operator=(boost::corosio::io_object::handle&&)
:182
366x
100.0%
62.5%
87.0%
boost::corosio::io_object::handle::operator bool() const
:204
161791x
100.0%
–
100.0%
boost::corosio::io_object::handle::service() const
:210
68388x
100.0%
–
100.0%
boost::corosio::io_object::handle::get() const
:216
2162996x
100.0%
–
100.0%
boost::corosio::io_object::handle::reset(boost::corosio::io_object::implementation*)
:233
11573x
100.0%
50.0%
83.0%
boost::corosio::io_object::handle::context() const
:244
37x
100.0%
–
100.0%
boost::corosio::io_object::context() const
:251
37x
100.0%
–
100.0%
boost::corosio::io_object::~io_object()
:258
68293x
100.0%
–
100.0%
boost::corosio::io_object::io_object()
:261
22x
100.0%
–
100.0%
boost::corosio::io_object::io_object(boost::corosio::io_object::handle)
:284
53488x
100.0%
–
100.0%
boost::corosio::io_object::io_object(boost::corosio::io_object&&)
:287
14782x
100.0%
–
100.0%
boost::corosio::io_object::operator=(boost::corosio::io_object&&)
:290
4x
100.0%
50.0%
100.0%
| Line | Branch | TLA | Hits | Source Code |
|---|---|---|---|---|
| 1 | // | |||
| 2 | // Copyright (c) 2025 Vinnie Falco ([email protected]) | |||
| 3 | // Copyright (c) 2026 Steve Gerbino | |||
| 4 | // Copyright (c) 2026 Michael Vandeberg | |||
| 5 | // | |||
| 6 | // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||
| 7 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||
| 8 | // | |||
| 9 | // Official repository: https://github.com/cppalliance/corosio | |||
| 10 | // | |||
| 11 | ||||
| 12 | #ifndef BOOST_COROSIO_IO_IO_OBJECT_HPP | |||
| 13 | #define BOOST_COROSIO_IO_IO_OBJECT_HPP | |||
| 14 | ||||
| 15 | #include <boost/corosio/detail/config.hpp> | |||
| 16 | #include <boost/corosio/detail/except.hpp> | |||
| 17 | #include <boost/capy/ex/execution_context.hpp> | |||
| 18 | ||||
| 19 | #include <atomic> | |||
| 20 | #include <cstddef> | |||
| 21 | #include <utility> | |||
| 22 | ||||
| 23 | namespace boost::corosio { | |||
| 24 | ||||
| 25 | /** Owns the platform-specific handle and execution context that a derived | |||
| 26 | socket, timer, signal handler, or acceptor type uses to dispatch | |||
| 27 | operations. | |||
| 28 | ||||
| 29 | Provides common infrastructure for I/O objects that wrap kernel | |||
| 30 | resources (sockets, timers, signal handlers, acceptors). Derived | |||
| 31 | classes dispatch operations through a platform-specific vtable | |||
| 32 | (IOCP, epoll, kqueue, io_uring). | |||
| 33 | ||||
| 34 | @par Semantics | |||
| 35 | Only concrete platform I/O types should inherit from `io_object`. | |||
| 36 | Test mocks, decorators, and stream adapters must not inherit from | |||
| 37 | this class. Use concepts or templates for generic I/O algorithms. | |||
| 38 | ||||
| 39 | @par Thread Safety | |||
| 40 | Distinct objects: Safe. | |||
| 41 | Shared objects: Unsafe. All operations on a single I/O object | |||
| 42 | must be serialized. | |||
| 43 | ||||
| 44 | @note Intended as a protected base class. The handle member | |||
| 45 | `h_` is accessible to derived classes. | |||
| 46 | ||||
| 47 | @see @ref io_stream, @ref tcp_socket, @ref tcp_acceptor | |||
| 48 | */ | |||
| 49 | class BOOST_COROSIO_DECL io_object | |||
| 50 | { | |||
| 51 | public: | |||
| 52 | class handle; | |||
| 53 | ||||
| 54 | /** Derived types dispatch platform-specific I/O operations through it. | |||
| 55 | ||||
| 56 | Reference-counted: the owning service holds one reference | |||
| 57 | (`refs_` starts at 1); in-flight operations hold additional | |||
| 58 | references through `detail::object_ref`. When the count reaches | |||
| 59 | zero, `retire()` runs — services recycle the impl into | |||
| 60 | their free-list there. | |||
| 61 | */ | |||
| 62 | 18892x | struct implementation | ||
| 63 | { | |||
| 64 | /// Destroy the implementation; called only through @ref io_service. | |||
| 65 | 18885x | virtual ~implementation() = default; | ||
| 66 | ||||
| 67 | /** Retire the implementation once the reference count reaches zero. | |||
| 68 | ||||
| 69 | Every concrete implementation's override follows the | |||
| 70 | same shape: recycle into the owning service's pool member | |||
| 71 | as the final statement. The service befriends the | |||
| 72 | implementation so it can reach that private member | |||
| 73 | directly. Any required last-rites cleanup — releasing | |||
| 74 | state that is only safe to tear down once idle — runs as | |||
| 75 | ordinary statements immediately before the recycle. | |||
| 76 | Whichever action runs — recycle or delete — must be the | |||
| 77 | final statement: nothing touches the implementation | |||
| 78 | after. | |||
| 79 | */ | |||
| 80 | virtual void retire() noexcept = 0; | |||
| 81 | ||||
| 82 | /// In-flight + service references; starts at the service's 1. | |||
| 83 | 18892x | std::atomic<std::size_t> refs_{1}; | ||
| 84 | }; | |||
| 85 | ||||
| 86 | /** Constructs, closes, and destroys platform implementations on | |||
| 87 | behalf of an I/O object. Platform backends implement this | |||
| 88 | interface. | |||
| 89 | */ | |||
| 90 | struct BOOST_COROSIO_DECL io_service | |||
| 91 | { | |||
| 92 | /// Destroy the service; the execution context outlives it. | |||
| 93 | 5961x | virtual ~io_service() = default; | ||
| 94 | ||||
| 95 | /** Construct a new implementation instance. | |||
| 96 | ||||
| 97 | May return a recycled implementation taken from the | |||
| 98 | service's free-list (populated by `implementation::retire`) | |||
| 99 | instead of allocating new storage. | |||
| 100 | */ | |||
| 101 | virtual implementation* construct() = 0; | |||
| 102 | ||||
| 103 | /** Close kernel resources and release the service's reference. | |||
| 104 | ||||
| 105 | Called whenever a handle relinquishes its current | |||
| 106 | implementation, not only on destruction. Handle | |||
| 107 | destruction, move-assignment onto a handle that already | |||
| 108 | owns one (the replaced implementation is destroyed, the | |||
| 109 | incoming one is not), and `handle::reset()` all invoke | |||
| 110 | this. It closes the underlying descriptor and drops the | |||
| 111 | service's own reference. In-flight operations may still | |||
| 112 | hold references of their own, so the implementation is | |||
| 113 | not necessarily recycled or freed here. That happens in | |||
| 114 | `implementation::retire` whenever the reference count | |||
| 115 | actually reaches zero. | |||
| 116 | */ | |||
| 117 | virtual void destroy(implementation* impl) = 0; | |||
| 118 | ||||
| 119 | /// Close the I/O object, releasing kernel resources without deallocating. | |||
| 120 | 18952x | virtual void close([[maybe_unused]] handle& h) {} | ||
| 121 | }; | |||
| 122 | ||||
| 123 | /** Owns a platform-specific I/O implementation and destroys it | |||
| 124 | when the handle goes out of scope. | |||
| 125 | */ | |||
| 126 | class handle | |||
| 127 | { | |||
| 128 | 22x | capy::execution_context* ctx_ = nullptr; | ||
| 129 | 22x | io_service* svc_ = nullptr; | ||
| 130 | 22x | implementation* impl_ = nullptr; | ||
| 131 | ||||
| 132 | public: | |||
| 133 | /// Destroy the handle and its implementation. | |||
| 134 | 250904x | ~handle() | ||
| 135 | 125451x | { | ||
| 136 |
2/2✓ Branch 0 taken 72310 times.
✓ Branch 1 taken 53143 times.
|
125453x | if (impl_) | |
| 137 | { | |||
| 138 |
2/2✓ Branch 0 taken 53139 times.
✓ Branch 1 taken 4 times.
|
53147x | svc_->close(*this); | |
| 139 |
2/2✓ Branch 0 taken 53143 times.
✓ Branch 1 taken 4 times.
|
53139x | svc_->destroy(impl_); | |
| 140 | 53143x | } | ||
| 141 | 250904x | } | ||
| 142 | ||||
| 143 | /// Construct an empty handle. | |||
| 144 | 66x | handle() = default; | ||
| 145 | ||||
| 146 | /** Construct a handle that takes over an existing implementation. | |||
| 147 | ||||
| 148 | @pre @p p, if non-null, was constructed by @p svc and is | |||
| 149 | owned by no other handle. | |||
| 150 | ||||
| 151 | @param ctx The context @p svc belongs to. | |||
| 152 | @param svc The service that constructed @p p. | |||
| 153 | @param p The implementation to own. | |||
| 154 | */ | |||
| 155 | 32x | handle( | ||
| 156 | capy::execution_context& ctx, | |||
| 157 | io_service& svc, | |||
| 158 | implementation* p) noexcept | |||
| 159 | 16x | : ctx_(&ctx) | ||
| 160 | 16x | , svc_(&svc) | ||
| 161 | 16x | , impl_(p) | ||
| 162 | 16x | { | ||
| 163 | 32x | } | ||
| 164 | ||||
| 165 | /// Construct a handle bound to a context and service. | |||
| 166 | 106977x | handle(capy::execution_context& ctx, io_service& svc) | ||
| 167 | 53488x | : ctx_(&ctx) | ||
| 168 | 53488x | , svc_(&svc) | ||
| 169 | 53488x | , impl_(svc_->construct()) | ||
| 170 | 53489x | { | ||
| 171 | 106977x | } | ||
| 172 | ||||
| 173 | /// Move construct from another handle. | |||
| 174 | 143866x | handle(handle&& other) noexcept | ||
| 175 | 71933x | : ctx_(std::exchange(other.ctx_, nullptr)) | ||
| 176 | 71933x | , svc_(std::exchange(other.svc_, nullptr)) | ||
| 177 | 71933x | , impl_(std::exchange(other.impl_, nullptr)) | ||
| 178 | 71933x | { | ||
| 179 | 143866x | } | ||
| 180 | ||||
| 181 | /// Move assign from another handle. | |||
| 182 | 366x | handle& operator=(handle&& other) noexcept | ||
| 183 | { | |||
| 184 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 349 times.
|
366x | if (this != &other) | |
| 185 | { | |||
| 186 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 348 times.
|
366x | if (impl_) | |
| 187 | { | |||
| 188 |
1/2✓ Branch 0 taken 348 times.
✗ Branch 1 not taken.
|
365x | svc_->close(*this); | |
| 189 |
1/2✓ Branch 0 taken 348 times.
✗ Branch 1 not taken.
|
365x | svc_->destroy(impl_); | |
| 190 | 365x | } | ||
| 191 | 366x | ctx_ = std::exchange(other.ctx_, nullptr); | ||
| 192 | 366x | svc_ = std::exchange(other.svc_, nullptr); | ||
| 193 | 366x | impl_ = std::exchange(other.impl_, nullptr); | ||
| 194 | 366x | } | ||
| 195 | 366x | return *this; | ||
| 196 | } | |||
| 197 | ||||
| 198 | /// Copy construction is disabled; the implementation is uniquely owned. | |||
| 199 | handle(handle const&) = delete; | |||
| 200 | /// Copy assignment is disabled; the implementation is uniquely owned. | |||
| 201 | handle& operator=(handle const&) = delete; | |||
| 202 | ||||
| 203 | /// Return true if the handle owns an implementation. | |||
| 204 | 161791x | explicit operator bool() const noexcept | ||
| 205 | { | |||
| 206 | 161791x | return impl_ != nullptr; | ||
| 207 | } | |||
| 208 | ||||
| 209 | /// Return the associated I/O service. | |||
| 210 | 68388x | io_service& service() const noexcept | ||
| 211 | { | |||
| 212 | 68388x | return *svc_; | ||
| 213 | } | |||
| 214 | ||||
| 215 | /// Return the platform implementation. | |||
| 216 | 2162996x | implementation* get() const noexcept | ||
| 217 | { | |||
| 218 | 2162996x | return impl_; | ||
| 219 | } | |||
| 220 | ||||
| 221 | /** Replace the implementation, destroying the old one. | |||
| 222 | ||||
| 223 | @pre The handle is already bound to a service (constructed | |||
| 224 | via the service-taking constructor, not the default | |||
| 225 | one) — the old implementation is destroyed through | |||
| 226 | that service. | |||
| 227 | @pre @p p, if non-null, was constructed by this handle's | |||
| 228 | own service. The service that destroys it later must | |||
| 229 | be the one that knows how to close it. | |||
| 230 | ||||
| 231 | @param p The new implementation to own. May be nullptr. | |||
| 232 | */ | |||
| 233 | 11573x | void reset(implementation* p) noexcept | ||
| 234 | { | |||
| 235 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 11468 times.
|
11573x | if (impl_) | |
| 236 | { | |||
| 237 |
1/2✓ Branch 0 taken 11468 times.
✗ Branch 1 not taken.
|
11573x | svc_->close(*this); | |
| 238 |
1/2✓ Branch 0 taken 11468 times.
✗ Branch 1 not taken.
|
11573x | svc_->destroy(impl_); | |
| 239 | 11573x | } | ||
| 240 | 11573x | impl_ = p; | ||
| 241 | 11573x | } | ||
| 242 | ||||
| 243 | /// Return the execution context. | |||
| 244 | 37x | capy::execution_context& context() const noexcept | ||
| 245 | { | |||
| 246 | 37x | return *ctx_; | ||
| 247 | } | |||
| 248 | }; | |||
| 249 | ||||
| 250 | /// Return the execution context. | |||
| 251 | 37x | capy::execution_context& context() const noexcept | ||
| 252 | { | |||
| 253 | 37x | return h_.context(); | ||
| 254 | } | |||
| 255 | ||||
| 256 | protected: | |||
| 257 | /// Destroy the object; protected, so only a derived type destroys one. | |||
| 258 | 68293x | virtual ~io_object() = default; | ||
| 259 | ||||
| 260 | /// Default construct for virtual base initialization. | |||
| 261 | 22x | io_object() noexcept = default; | ||
| 262 | ||||
| 263 | /** Create a handle bound to a service found in the context. | |||
| 264 | ||||
| 265 | @tparam Service The service type whose `key_type` is used for | |||
| 266 | lookup. | |||
| 267 | @param ctx The execution context to search for the service. | |||
| 268 | ||||
| 269 | @return A handle owning a freshly constructed implementation. | |||
| 270 | ||||
| 271 | @throws std::logic_error if the service is not installed. | |||
| 272 | */ | |||
| 273 | template<class Service> | |||
| 274 | static handle create_handle(capy::execution_context& ctx) | |||
| 275 | { | |||
| 276 | auto* svc = ctx.find_service<Service>(); | |||
| 277 | if (!svc) | |||
| 278 | detail::throw_logic_error( | |||
| 279 | "io_object::create_handle: service not installed"); | |||
| 280 | return handle(ctx, *svc); | |||
| 281 | } | |||
| 282 | ||||
| 283 | /// Construct an I/O object from a handle. | |||
| 284 | 53488x | explicit io_object(handle h) noexcept : h_(std::move(h)) {} | ||
| 285 | ||||
| 286 | /// Move construct from another I/O object. | |||
| 287 | 14782x | io_object(io_object&& other) noexcept : h_(std::move(other.h_)) {} | ||
| 288 | ||||
| 289 | /// Move assign from another I/O object. | |||
| 290 | 4x | io_object& operator=(io_object&& other) noexcept | ||
| 291 | { | |||
| 292 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4x | if (this != &other) | |
| 293 | 4x | h_ = std::move(other.h_); | ||
| 294 | 4x | return *this; | ||
| 295 | } | |||
| 296 | ||||
| 297 | /// Copy construction is disabled; the handle is uniquely owned. | |||
| 298 | io_object(io_object const&) = delete; | |||
| 299 | /// Copy assignment is disabled; the handle is uniquely owned. | |||
| 300 | io_object& operator=(io_object const&) = delete; | |||
| 301 | ||||
| 302 | /// The platform I/O handle owned by this object. | |||
| 303 | BOOST_COROSIO_MSVC_WARNING_PUSH | |||
| 304 | BOOST_COROSIO_MSVC_WARNING_DISABLE(4251) | |||
| 305 | handle h_; | |||
| 306 | BOOST_COROSIO_MSVC_WARNING_POP | |||
| 307 | }; | |||
| 308 | ||||
| 309 | } // namespace boost::corosio | |||
| 310 | ||||
| 311 | #endif | |||
| 312 |