TLA Line data 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 HIT 18158 : 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 5575 : 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 24179 : 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 110412 : ~handle()
140 : {
141 110412 : if (impl_)
142 : {
143 48656 : svc_->close(*this);
144 48656 : svc_->destroy(impl_);
145 : }
146 110412 : }
147 :
148 : /// Construct an empty handle.
149 22 : 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 16 : handle(
161 : capy::execution_context& ctx,
162 : io_service& svc,
163 : implementation* p) noexcept
164 16 : : ctx_(&ctx)
165 16 : , svc_(&svc)
166 16 : , impl_(p)
167 : {
168 16 : }
169 :
170 : /// Construct a handle bound to a context and service.
171 48687 : handle(capy::execution_context& ctx, io_service& svc)
172 48687 : : ctx_(&ctx)
173 48687 : , svc_(&svc)
174 48687 : , impl_(svc_->construct())
175 : {
176 48687 : }
177 :
178 : /// Move construct from another handle.
179 61687 : handle(handle&& other) noexcept
180 61687 : : ctx_(std::exchange(other.ctx_, nullptr))
181 61687 : , svc_(std::exchange(other.svc_, nullptr))
182 61687 : , impl_(std::exchange(other.impl_, nullptr))
183 : {
184 61687 : }
185 :
186 : /// Move assign from another handle.
187 48 : handle& operator=(handle&& other) noexcept
188 : {
189 48 : if (this != &other)
190 : {
191 48 : if (impl_)
192 : {
193 47 : svc_->close(*this);
194 47 : svc_->destroy(impl_);
195 : }
196 48 : ctx_ = std::exchange(other.ctx_, nullptr);
197 48 : svc_ = std::exchange(other.svc_, nullptr);
198 48 : impl_ = std::exchange(other.impl_, nullptr);
199 : }
200 48 : 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 101186 : explicit operator bool() const noexcept
210 : {
211 101186 : return impl_ != nullptr;
212 : }
213 :
214 : /// Return the associated I/O service.
215 45940 : io_service& service() const noexcept
216 : {
217 45940 : return *svc_;
218 : }
219 :
220 : /// Return the platform implementation.
221 1113986 : implementation* get() const noexcept
222 : {
223 1113986 : 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 6341 : void reset(implementation* p) noexcept
239 : {
240 6341 : if (impl_)
241 : {
242 6341 : svc_->close(*this);
243 6341 : svc_->destroy(impl_);
244 : }
245 6341 : impl_ = p;
246 6341 : }
247 :
248 : /// Return the execution context.
249 37 : capy::execution_context& context() const noexcept
250 : {
251 37 : return *ctx_;
252 : }
253 : };
254 :
255 : /// Return the execution context.
256 37 : capy::execution_context& context() const noexcept
257 : {
258 37 : return h_.context();
259 : }
260 :
261 : protected:
262 : /// Destroy the object; protected, so only a derived type destroys one.
263 58050 : virtual ~io_object() = default;
264 :
265 : /// Default construct for virtual base initialization.
266 22 : 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 48687 : explicit io_object(handle h) noexcept : h_(std::move(h)) {}
290 :
291 : /// Move construct from another I/O object.
292 9341 : io_object(io_object&& other) noexcept : h_(std::move(other.h_)) {}
293 :
294 : /// Move assign from another I/O object.
295 4 : io_object& operator=(io_object&& other) noexcept
296 : {
297 4 : if (this != &other)
298 4 : h_ = std::move(other.h_);
299 4 : 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
|