include/boost/corosio/io/io_object.hpp

100.0% Lines (57 / 57) 100.0% Functions (20 / 20)
io_object.hpp
f(x) Functions (20)
Function Calls Lines Blocks
boost::corosio::io_object::implementation::~implementation() :70 17132x 100.0% 100.0% boost::corosio::io_object::io_service::~io_service() :98 5575x 100.0% 100.0% boost::corosio::io_object::io_service::close(boost::corosio::io_object::handle&) :125 23252x 100.0% 100.0% boost::corosio::io_object::handle::~handle() :139 107872x 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 47413x 100.0% 100.0% boost::corosio::io_object::handle::handle(boost::corosio::io_object::handle&&) :179 60421x 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 100119x 100.0% 100.0% boost::corosio::io_object::handle::service() const :215 45359x 100.0% 100.0% boost::corosio::io_object::handle::get() const :221 1019230x 100.0% 100.0% boost::corosio::io_object::handle::reset(boost::corosio::io_object::implementation*) :238 6542x 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 56784x 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 47413x 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 17132x 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 5575x 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 23252x 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 107872x ~handle()
140 {
141 107872x if (impl_)
142 {
143 47382x svc_->close(*this);
144 47382x svc_->destroy(impl_);
145 }
146 107872x }
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 47413x handle(capy::execution_context& ctx, io_service& svc)
172 47413x : ctx_(&ctx)
173 47413x , svc_(&svc)
174 47413x , impl_(svc_->construct())
175 {
176 47413x }
177
178 /// Move construct from another handle.
179 60421x handle(handle&& other) noexcept
180 60421x : ctx_(std::exchange(other.ctx_, nullptr))
181 60421x , svc_(std::exchange(other.svc_, nullptr))
182 60421x , impl_(std::exchange(other.impl_, nullptr))
183 {
184 60421x }
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 100119x explicit operator bool() const noexcept
210 {
211 100119x return impl_ != nullptr;
212 }
213
214 /// Return the associated I/O service.
215 45359x io_service& service() const noexcept
216 {
217 45359x return *svc_;
218 }
219
220 /// Return the platform implementation.
221 1019230x implementation* get() const noexcept
222 {
223 1019230x 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 6542x void reset(implementation* p) noexcept
239 {
240 6542x if (impl_)
241 {
242 6542x svc_->close(*this);
243 6542x svc_->destroy(impl_);
244 }
245 6542x impl_ = p;
246 6542x }
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 56784x 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 47413x 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