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 : @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 : struct implementation
63 : {
64 : /// Destroy the implementation; called only through @ref io_service.
65 HIT 17127 : 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 : 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 8903 : 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 23642 : 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 : capy::execution_context* ctx_ = nullptr;
129 : io_service* svc_ = nullptr;
130 : implementation* impl_ = nullptr;
131 :
132 : public:
133 : /// Destroy the handle and its implementation.
134 103915 : ~handle()
135 : {
136 103915 : if (impl_)
137 : {
138 47052 : svc_->close(*this);
139 47052 : svc_->destroy(impl_);
140 : }
141 103915 : }
142 :
143 : /// Construct an empty handle.
144 10 : 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 8 : handle(
156 : capy::execution_context& ctx,
157 : io_service& svc,
158 : implementation* p) noexcept
159 8 : : ctx_(&ctx)
160 8 : , svc_(&svc)
161 8 : , impl_(p)
162 : {
163 8 : }
164 :
165 : /// Construct a handle bound to a context and service.
166 47086 : handle(capy::execution_context& ctx, io_service& svc)
167 47086 : : ctx_(&ctx)
168 47086 : , svc_(&svc)
169 47086 : , impl_(svc_->construct())
170 : {
171 47086 : }
172 :
173 : /// Move construct from another handle.
174 56811 : handle(handle&& other) noexcept
175 56811 : : ctx_(std::exchange(other.ctx_, nullptr))
176 56811 : , svc_(std::exchange(other.svc_, nullptr))
177 56811 : , impl_(std::exchange(other.impl_, nullptr))
178 : {
179 56811 : }
180 :
181 : /// Move assign from another handle.
182 43 : handle& operator=(handle&& other) noexcept
183 : {
184 43 : if (this != &other)
185 : {
186 43 : if (impl_)
187 : {
188 42 : svc_->close(*this);
189 42 : svc_->destroy(impl_);
190 : }
191 43 : ctx_ = std::exchange(other.ctx_, nullptr);
192 43 : svc_ = std::exchange(other.svc_, nullptr);
193 43 : impl_ = std::exchange(other.impl_, nullptr);
194 : }
195 43 : 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 93626 : explicit operator bool() const noexcept
205 : {
206 93626 : return impl_ != nullptr;
207 : }
208 :
209 : /// Return the associated I/O service.
210 43994 : io_service& service() const noexcept
211 : {
212 43994 : return *svc_;
213 : }
214 :
215 : /// Return the platform implementation.
216 1128052 : implementation* get() const noexcept
217 : {
218 1128052 : 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 6227 : void reset(implementation* p) noexcept
234 : {
235 6227 : if (impl_)
236 : {
237 6227 : svc_->close(*this);
238 6227 : svc_->destroy(impl_);
239 : }
240 6227 : impl_ = p;
241 6227 : }
242 :
243 : /// Return the execution context.
244 41 : capy::execution_context& context() const noexcept
245 : {
246 41 : return *ctx_;
247 : }
248 : };
249 :
250 : /// Return the execution context.
251 41 : capy::execution_context& context() const noexcept
252 : {
253 41 : return h_.context();
254 : }
255 :
256 : protected:
257 : /// Destroy the object; protected, so only a derived type destroys one.
258 53202 : virtual ~io_object() = default;
259 :
260 : /// Default construct for virtual base initialization.
261 10 : 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 20026 : static handle create_handle(capy::execution_context& ctx)
275 : {
276 20026 : auto* svc = ctx.find_service<Service>();
277 20026 : if (!svc)
278 4 : detail::throw_logic_error(
279 : "io_object::create_handle: service not installed");
280 20022 : return handle(ctx, *svc);
281 : }
282 :
283 : /// Construct an I/O object from a handle.
284 47086 : explicit io_object(handle h) noexcept : h_(std::move(h)) {}
285 :
286 : /// Move construct from another I/O object.
287 6106 : io_object(io_object&& other) noexcept : h_(std::move(other.h_)) {}
288 :
289 : /// Move assign from another I/O object.
290 4 : io_object& operator=(io_object&& other) noexcept
291 : {
292 4 : if (this != &other)
293 4 : h_ = std::move(other.h_);
294 4 : 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
|