TLA Line data 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 HIT 17668 : 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 8907 : 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 24019 : 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 105857 : ~handle()
134 : {
135 105857 : if (impl_)
136 : {
137 48024 : svc_->close(*this);
138 48024 : svc_->destroy(impl_);
139 : }
140 105857 : }
141 :
142 : /// Construct an empty handle.
143 10 : handle() = default;
144 :
145 : /** Construct a handle that takes over an existing implementation.
146 :
147 : @pre @p p, if non-null, was constructed by @p svc and is
148 : owned by no other handle.
149 :
150 : @param ctx The context @p svc belongs to.
151 : @param svc The service that constructed @p p.
152 : @param p The implementation to own.
153 : */
154 8 : handle(
155 : capy::execution_context& ctx,
156 : io_service& svc,
157 : implementation* p) noexcept
158 8 : : ctx_(&ctx)
159 8 : , svc_(&svc)
160 8 : , impl_(p)
161 : {
162 8 : }
163 :
164 : /// Construct a handle bound to a context and service.
165 48058 : handle(capy::execution_context& ctx, io_service& svc)
166 48058 : : ctx_(&ctx)
167 48058 : , svc_(&svc)
168 48058 : , impl_(svc_->construct())
169 : {
170 48058 : }
171 :
172 : /// Move construct from another handle.
173 57781 : handle(handle&& other) noexcept
174 57781 : : ctx_(std::exchange(other.ctx_, nullptr))
175 57781 : , svc_(std::exchange(other.svc_, nullptr))
176 57781 : , impl_(std::exchange(other.impl_, nullptr))
177 : {
178 57781 : }
179 :
180 : /// Move assign from another handle.
181 43 : handle& operator=(handle&& other) noexcept
182 : {
183 43 : if (this != &other)
184 : {
185 43 : if (impl_)
186 : {
187 42 : svc_->close(*this);
188 42 : svc_->destroy(impl_);
189 : }
190 43 : ctx_ = std::exchange(other.ctx_, nullptr);
191 43 : svc_ = std::exchange(other.svc_, nullptr);
192 43 : impl_ = std::exchange(other.impl_, nullptr);
193 : }
194 43 : return *this;
195 : }
196 :
197 : /// Copy construction is disabled; the implementation is uniquely owned.
198 : handle(handle const&) = delete;
199 : /// Copy assignment is disabled; the implementation is uniquely owned.
200 : handle& operator=(handle const&) = delete;
201 :
202 : /// Return true if the handle owns an implementation.
203 96285 : explicit operator bool() const noexcept
204 : {
205 96285 : return impl_ != nullptr;
206 : }
207 :
208 : /// Return the associated I/O service.
209 45186 : io_service& service() const noexcept
210 : {
211 45186 : return *svc_;
212 : }
213 :
214 : /// Return the platform implementation.
215 1140330 : implementation* get() const noexcept
216 : {
217 1140330 : return impl_;
218 : }
219 :
220 : /** Replace the implementation, destroying the old one.
221 :
222 : @pre The handle is already bound to a service (constructed
223 : via the service-taking constructor, not the default
224 : one) — the old implementation is destroyed through
225 : that service.
226 : @pre @p p, if non-null, was constructed by this handle's
227 : own service. The service that destroys it later must
228 : be the one that knows how to close it.
229 :
230 : @param p The new implementation to own. May be nullptr.
231 : */
232 6503 : void reset(implementation* p) noexcept
233 : {
234 6503 : if (impl_)
235 : {
236 6503 : svc_->close(*this);
237 6503 : svc_->destroy(impl_);
238 : }
239 6503 : impl_ = p;
240 6503 : }
241 :
242 : /// Return the execution context.
243 41 : capy::execution_context& context() const noexcept
244 : {
245 41 : return *ctx_;
246 : }
247 : };
248 :
249 : /// Return the execution context.
250 41 : capy::execution_context& context() const noexcept
251 : {
252 41 : return h_.context();
253 : }
254 :
255 : protected:
256 : /// Destroy the object; protected, so only a derived type destroys one.
257 54174 : virtual ~io_object() = default;
258 :
259 : /// Default construct for virtual base initialization.
260 10 : io_object() noexcept = default;
261 :
262 : /** Create a handle bound to a service found in the context.
263 :
264 : @tparam Service The service type whose `key_type` is used for
265 : lookup.
266 : @param ctx The execution context to search for the service.
267 :
268 : @return A handle owning a freshly constructed implementation.
269 :
270 : @throws std::logic_error if the service is not installed.
271 : */
272 : template<class Service>
273 20405 : static handle create_handle(capy::execution_context& ctx)
274 : {
275 20405 : auto* svc = ctx.find_service<Service>();
276 20405 : if (!svc)
277 4 : detail::throw_logic_error(
278 : "io_object::create_handle: service not installed");
279 20401 : return handle(ctx, *svc);
280 : }
281 :
282 : /// Construct an I/O object from a handle.
283 48058 : explicit io_object(handle h) noexcept : h_(std::move(h)) {}
284 :
285 : /// Move construct from another I/O object.
286 6106 : io_object(io_object&& other) noexcept : h_(std::move(other.h_)) {}
287 :
288 : /// Move assign from another I/O object.
289 4 : io_object& operator=(io_object&& other) noexcept
290 : {
291 4 : if (this != &other)
292 4 : h_ = std::move(other.h_);
293 4 : return *this;
294 : }
295 :
296 : /// Copy construction is disabled; the handle is uniquely owned.
297 : io_object(io_object const&) = delete;
298 : /// Copy assignment is disabled; the handle is uniquely owned.
299 : io_object& operator=(io_object const&) = delete;
300 :
301 : /// The platform I/O handle owned by this object.
302 : BOOST_COROSIO_MSVC_WARNING_PUSH
303 : BOOST_COROSIO_MSVC_WARNING_DISABLE(4251)
304 : handle h_;
305 : BOOST_COROSIO_MSVC_WARNING_POP
306 : };
307 :
308 : } // namespace boost::corosio
309 :
310 : #endif
|