include/boost/corosio/io/io_object.hpp

100.0% Lines (70 / 70) 68.2% Branches (15 / 22) 100.0% Functions (22 / 22, 1 excl)
io_object.hpp
f(x) 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