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