include/boost/corosio/io/io_object.hpp
100.0% Lines (57 / 57)
100.0% Functions (20 / 20)
Functions (20)
Function
Calls
Lines
Blocks
boost::corosio::io_object::implementation::~implementation()
:70
17831x
100.0%
100.0%
boost::corosio::io_object::io_service::~io_service()
:98
5569x
100.0%
100.0%
boost::corosio::io_object::io_service::close(boost::corosio::io_object::handle&)
:125
23916x
100.0%
100.0%
boost::corosio::io_object::handle::~handle()
:139
109402x
100.0%
100.0%
boost::corosio::io_object::handle::handle()
:149
22x
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*)
:160
16x
100.0%
100.0%
boost::corosio::io_object::handle::handle(boost::capy::execution_context&, boost::corosio::io_object::io_service&)
:171
48178x
100.0%
100.0%
boost::corosio::io_object::handle::handle(boost::corosio::io_object::handle&&)
:179
61186x
100.0%
100.0%
boost::corosio::io_object::handle::operator=(boost::corosio::io_object::handle&&)
:187
48x
100.0%
100.0%
boost::corosio::io_object::handle::operator bool() const
:209
100396x
100.0%
100.0%
boost::corosio::io_object::handle::service() const
:215
45426x
100.0%
100.0%
boost::corosio::io_object::handle::get() const
:221
1093106x
100.0%
100.0%
boost::corosio::io_object::handle::reset(boost::corosio::io_object::implementation*)
:238
6549x
100.0%
100.0%
boost::corosio::io_object::handle::context() const
:249
37x
100.0%
100.0%
boost::corosio::io_object::context() const
:256
37x
100.0%
100.0%
boost::corosio::io_object::~io_object()
:263
57549x
100.0%
100.0%
boost::corosio::io_object::io_object()
:266
22x
100.0%
100.0%
boost::corosio::io_object::io_object(boost::corosio::io_object::handle)
:289
48178x
100.0%
100.0%
boost::corosio::io_object::io_object(boost::corosio::io_object&&)
:292
9349x
100.0%
100.0%
boost::corosio::io_object::operator=(boost::corosio::io_object&&)
:295
4x
100.0%
100.0%
| Line | TLA | Hits | Source Code |
|---|---|---|---|
| 1 | // | ||
| 2 | // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) | ||
| 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 | @par Executor Affinity | ||
| 45 | An operation resumes the awaiting coroutine through that | ||
| 46 | coroutine's executor, not through the executor of the context | ||
| 47 | that owns the I/O object. | ||
| 48 | |||
| 49 | @note Intended as a protected base class. The handle member | ||
| 50 | `h_` is accessible to derived classes. | ||
| 51 | |||
| 52 | @see @ref io_stream, @ref tcp_socket, @ref tcp_acceptor | ||
| 53 | */ | ||
| 54 | class BOOST_COROSIO_DECL io_object | ||
| 55 | { | ||
| 56 | public: | ||
| 57 | class handle; | ||
| 58 | |||
| 59 | /** Derived types dispatch platform-specific I/O operations through it. | ||
| 60 | |||
| 61 | Reference-counted: the owning service holds one reference | ||
| 62 | (`refs_` starts at 1); in-flight operations hold additional | ||
| 63 | references through `detail::object_ref`. When the count reaches | ||
| 64 | zero, `retire()` runs — services recycle the impl into | ||
| 65 | their free-list there. | ||
| 66 | */ | ||
| 67 | struct implementation | ||
| 68 | { | ||
| 69 | /// Destroy the implementation; called only through @ref io_service. | ||
| 70 | 17831x | virtual ~implementation() = default; | |
| 71 | |||
| 72 | /** Retire the implementation once the reference count reaches zero. | ||
| 73 | |||
| 74 | Every concrete implementation's override follows the | ||
| 75 | same shape: recycle into the owning service's pool member | ||
| 76 | as the final statement. The service befriends the | ||
| 77 | implementation so it can reach that private member | ||
| 78 | directly. Any required last-rites cleanup — releasing | ||
| 79 | state that is only safe to tear down once idle — runs as | ||
| 80 | ordinary statements immediately before the recycle. | ||
| 81 | Whichever action runs — recycle or delete — must be the | ||
| 82 | final statement: nothing touches the implementation | ||
| 83 | after. | ||
| 84 | */ | ||
| 85 | virtual void retire() noexcept = 0; | ||
| 86 | |||
| 87 | /// In-flight + service references; starts at the service's 1. | ||
| 88 | std::atomic<std::size_t> refs_{1}; | ||
| 89 | }; | ||
| 90 | |||
| 91 | /** Constructs, closes, and destroys platform implementations on | ||
| 92 | behalf of an I/O object. Platform backends implement this | ||
| 93 | interface. | ||
| 94 | */ | ||
| 95 | struct BOOST_COROSIO_DECL io_service | ||
| 96 | { | ||
| 97 | /// Destroy the service; the execution context outlives it. | ||
| 98 | 5569x | virtual ~io_service() = default; | |
| 99 | |||
| 100 | /** Construct a new implementation instance. | ||
| 101 | |||
| 102 | May return a recycled implementation taken from the | ||
| 103 | service's free-list (populated by `implementation::retire`) | ||
| 104 | instead of allocating new storage. | ||
| 105 | */ | ||
| 106 | virtual implementation* construct() = 0; | ||
| 107 | |||
| 108 | /** Close kernel resources and release the service's reference. | ||
| 109 | |||
| 110 | Called whenever a handle relinquishes its current | ||
| 111 | implementation, not only on destruction. Handle | ||
| 112 | destruction, move-assignment onto a handle that already | ||
| 113 | owns one (the replaced implementation is destroyed, the | ||
| 114 | incoming one is not), and `handle::reset()` all invoke | ||
| 115 | this. It closes the underlying descriptor and drops the | ||
| 116 | service's own reference. In-flight operations may still | ||
| 117 | hold references of their own, so the implementation is | ||
| 118 | not necessarily recycled or freed here. That happens in | ||
| 119 | `implementation::retire` whenever the reference count | ||
| 120 | actually reaches zero. | ||
| 121 | */ | ||
| 122 | virtual void destroy(implementation* impl) = 0; | ||
| 123 | |||
| 124 | /// Close the I/O object, releasing kernel resources without deallocating. | ||
| 125 | 23916x | virtual void close([[maybe_unused]] handle& h) {} | |
| 126 | }; | ||
| 127 | |||
| 128 | /** Owns a platform-specific I/O implementation and destroys it | ||
| 129 | when the handle goes out of scope. | ||
| 130 | */ | ||
| 131 | class handle | ||
| 132 | { | ||
| 133 | capy::execution_context* ctx_ = nullptr; | ||
| 134 | io_service* svc_ = nullptr; | ||
| 135 | implementation* impl_ = nullptr; | ||
| 136 | |||
| 137 | public: | ||
| 138 | /// Destroy the handle and its implementation. | ||
| 139 | 109402x | ~handle() | |
| 140 | { | ||
| 141 | 109402x | if (impl_) | |
| 142 | { | ||
| 143 | 48147x | svc_->close(*this); | |
| 144 | 48147x | svc_->destroy(impl_); | |
| 145 | } | ||
| 146 | 109402x | } | |
| 147 | |||
| 148 | /// Construct an empty handle. | ||
| 149 | 22x | handle() = default; | |
| 150 | |||
| 151 | /** Construct a handle that takes over an existing implementation. | ||
| 152 | |||
| 153 | @pre @p p, if non-null, was constructed by @p svc and is | ||
| 154 | owned by no other handle. | ||
| 155 | |||
| 156 | @param ctx The context @p svc belongs to. | ||
| 157 | @param svc The service that constructed @p p. | ||
| 158 | @param p The implementation to own. | ||
| 159 | */ | ||
| 160 | 16x | handle( | |
| 161 | capy::execution_context& ctx, | ||
| 162 | io_service& svc, | ||
| 163 | implementation* p) noexcept | ||
| 164 | 16x | : ctx_(&ctx) | |
| 165 | 16x | , svc_(&svc) | |
| 166 | 16x | , impl_(p) | |
| 167 | { | ||
| 168 | 16x | } | |
| 169 | |||
| 170 | /// Construct a handle bound to a context and service. | ||
| 171 | 48178x | handle(capy::execution_context& ctx, io_service& svc) | |
| 172 | 48178x | : ctx_(&ctx) | |
| 173 | 48178x | , svc_(&svc) | |
| 174 | 48178x | , impl_(svc_->construct()) | |
| 175 | { | ||
| 176 | 48178x | } | |
| 177 | |||
| 178 | /// Move construct from another handle. | ||
| 179 | 61186x | handle(handle&& other) noexcept | |
| 180 | 61186x | : ctx_(std::exchange(other.ctx_, nullptr)) | |
| 181 | 61186x | , svc_(std::exchange(other.svc_, nullptr)) | |
| 182 | 61186x | , impl_(std::exchange(other.impl_, nullptr)) | |
| 183 | { | ||
| 184 | 61186x | } | |
| 185 | |||
| 186 | /// Move assign from another handle. | ||
| 187 | 48x | handle& operator=(handle&& other) noexcept | |
| 188 | { | ||
| 189 | 48x | if (this != &other) | |
| 190 | { | ||
| 191 | 48x | if (impl_) | |
| 192 | { | ||
| 193 | 47x | svc_->close(*this); | |
| 194 | 47x | svc_->destroy(impl_); | |
| 195 | } | ||
| 196 | 48x | ctx_ = std::exchange(other.ctx_, nullptr); | |
| 197 | 48x | svc_ = std::exchange(other.svc_, nullptr); | |
| 198 | 48x | impl_ = std::exchange(other.impl_, nullptr); | |
| 199 | } | ||
| 200 | 48x | return *this; | |
| 201 | } | ||
| 202 | |||
| 203 | /// Copy construction is disabled; the implementation is uniquely owned. | ||
| 204 | handle(handle const&) = delete; | ||
| 205 | /// Copy assignment is disabled; the implementation is uniquely owned. | ||
| 206 | handle& operator=(handle const&) = delete; | ||
| 207 | |||
| 208 | /// Return true if the handle owns an implementation. | ||
| 209 | 100396x | explicit operator bool() const noexcept | |
| 210 | { | ||
| 211 | 100396x | return impl_ != nullptr; | |
| 212 | } | ||
| 213 | |||
| 214 | /// Return the associated I/O service. | ||
| 215 | 45426x | io_service& service() const noexcept | |
| 216 | { | ||
| 217 | 45426x | return *svc_; | |
| 218 | } | ||
| 219 | |||
| 220 | /// Return the platform implementation. | ||
| 221 | 1093106x | implementation* get() const noexcept | |
| 222 | { | ||
| 223 | 1093106x | return impl_; | |
| 224 | } | ||
| 225 | |||
| 226 | /** Replace the implementation, destroying the old one. | ||
| 227 | |||
| 228 | @pre The handle is already bound to a service (constructed | ||
| 229 | via the service-taking constructor, not the default | ||
| 230 | one) — the old implementation is destroyed through | ||
| 231 | that service. | ||
| 232 | @pre @p p, if non-null, was constructed by this handle's | ||
| 233 | own service. The service that destroys it later must | ||
| 234 | be the one that knows how to close it. | ||
| 235 | |||
| 236 | @param p The new implementation to own. May be nullptr. | ||
| 237 | */ | ||
| 238 | 6549x | void reset(implementation* p) noexcept | |
| 239 | { | ||
| 240 | 6549x | if (impl_) | |
| 241 | { | ||
| 242 | 6549x | svc_->close(*this); | |
| 243 | 6549x | svc_->destroy(impl_); | |
| 244 | } | ||
| 245 | 6549x | impl_ = p; | |
| 246 | 6549x | } | |
| 247 | |||
| 248 | /// Return the execution context. | ||
| 249 | 37x | capy::execution_context& context() const noexcept | |
| 250 | { | ||
| 251 | 37x | return *ctx_; | |
| 252 | } | ||
| 253 | }; | ||
| 254 | |||
| 255 | /// Return the execution context. | ||
| 256 | 37x | capy::execution_context& context() const noexcept | |
| 257 | { | ||
| 258 | 37x | return h_.context(); | |
| 259 | } | ||
| 260 | |||
| 261 | protected: | ||
| 262 | /// Destroy the object; protected, so only a derived type destroys one. | ||
| 263 | 57549x | virtual ~io_object() = default; | |
| 264 | |||
| 265 | /// Default construct for virtual base initialization. | ||
| 266 | 22x | io_object() noexcept = default; | |
| 267 | |||
| 268 | /** Create a handle bound to a service found in the context. | ||
| 269 | |||
| 270 | @tparam Service The service type whose `key_type` is used for | ||
| 271 | lookup. | ||
| 272 | @param ctx The execution context to search for the service. | ||
| 273 | |||
| 274 | @return A handle owning a freshly constructed implementation. | ||
| 275 | |||
| 276 | @throws std::logic_error if the service is not installed. | ||
| 277 | */ | ||
| 278 | template<class Service> | ||
| 279 | static handle create_handle(capy::execution_context& ctx) | ||
| 280 | { | ||
| 281 | auto* svc = ctx.find_service<Service>(); | ||
| 282 | if (!svc) | ||
| 283 | detail::throw_logic_error( | ||
| 284 | "io_object::create_handle: service not installed"); | ||
| 285 | return handle(ctx, *svc); | ||
| 286 | } | ||
| 287 | |||
| 288 | /// Construct an I/O object from a handle. | ||
| 289 | 48178x | explicit io_object(handle h) noexcept : h_(std::move(h)) {} | |
| 290 | |||
| 291 | /// Move construct from another I/O object. | ||
| 292 | 9349x | io_object(io_object&& other) noexcept : h_(std::move(other.h_)) {} | |
| 293 | |||
| 294 | /// Move assign from another I/O object. | ||
| 295 | 4x | io_object& operator=(io_object&& other) noexcept | |
| 296 | { | ||
| 297 | 4x | if (this != &other) | |
| 298 | 4x | h_ = std::move(other.h_); | |
| 299 | 4x | return *this; | |
| 300 | } | ||
| 301 | |||
| 302 | /// Copy construction is disabled; the handle is uniquely owned. | ||
| 303 | io_object(io_object const&) = delete; | ||
| 304 | /// Copy assignment is disabled; the handle is uniquely owned. | ||
| 305 | io_object& operator=(io_object const&) = delete; | ||
| 306 | |||
| 307 | /// The platform I/O handle owned by this object. | ||
| 308 | BOOST_COROSIO_MSVC_WARNING_PUSH | ||
| 309 | BOOST_COROSIO_MSVC_WARNING_DISABLE(4251) | ||
| 310 | handle h_; | ||
| 311 | BOOST_COROSIO_MSVC_WARNING_POP | ||
| 312 | }; | ||
| 313 | |||
| 314 | } // namespace boost::corosio | ||
| 315 | |||
| 316 | #endif | ||
| 317 |