100.00% Lines (57/57) 100.00% Functions (20/20)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) 2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3   // Copyright (c) 2026 Steve Gerbino 3   // Copyright (c) 2026 Steve Gerbino
4   // Copyright (c) 2026 Michael Vandeberg 4   // Copyright (c) 2026 Michael Vandeberg
5   // 5   //
6   // Distributed under the Boost Software License, Version 1.0. (See accompanying 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) 7   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
8   // 8   //
9   // Official repository: https://github.com/cppalliance/corosio 9   // Official repository: https://github.com/cppalliance/corosio
10   // 10   //
11   11  
12   #ifndef BOOST_COROSIO_IO_IO_OBJECT_HPP 12   #ifndef BOOST_COROSIO_IO_IO_OBJECT_HPP
13   #define BOOST_COROSIO_IO_IO_OBJECT_HPP 13   #define BOOST_COROSIO_IO_IO_OBJECT_HPP
14   14  
15   #include <boost/corosio/detail/config.hpp> 15   #include <boost/corosio/detail/config.hpp>
16   #include <boost/corosio/detail/except.hpp> 16   #include <boost/corosio/detail/except.hpp>
17   #include <boost/capy/ex/execution_context.hpp> 17   #include <boost/capy/ex/execution_context.hpp>
18   18  
19   #include <atomic> 19   #include <atomic>
20   #include <cstddef> 20   #include <cstddef>
21   #include <utility> 21   #include <utility>
22   22  
23   namespace boost::corosio { 23   namespace boost::corosio {
24   24  
25   /** Owns the platform-specific handle and execution context that a derived 25   /** Owns the platform-specific handle and execution context that a derived
26   socket, timer, signal handler, or acceptor type uses to dispatch 26   socket, timer, signal handler, or acceptor type uses to dispatch
27   operations. 27   operations.
28   28  
29   Provides common infrastructure for I/O objects that wrap kernel 29   Provides common infrastructure for I/O objects that wrap kernel
30   resources (sockets, timers, signal handlers, acceptors). Derived 30   resources (sockets, timers, signal handlers, acceptors). Derived
31   classes dispatch operations through a platform-specific vtable 31   classes dispatch operations through a platform-specific vtable
32   (IOCP, epoll, kqueue, io_uring). 32   (IOCP, epoll, kqueue, io_uring).
33   33  
34   @par Semantics 34   @par Semantics
35   Only concrete platform I/O types should inherit from `io_object`. 35   Only concrete platform I/O types should inherit from `io_object`.
36   Test mocks, decorators, and stream adapters must not inherit from 36   Test mocks, decorators, and stream adapters must not inherit from
37   this class. Use concepts or templates for generic I/O algorithms. 37   this class. Use concepts or templates for generic I/O algorithms.
38   38  
39   @par Thread Safety 39   @par Thread Safety
40   Distinct objects: Safe. 40   Distinct objects: Safe.
41   Shared objects: Unsafe. All operations on a single I/O object 41   Shared objects: Unsafe. All operations on a single I/O object
42   must be serialized. 42   must be serialized.
43   43  
44   @par Executor Affinity 44   @par Executor Affinity
45   An operation resumes the awaiting coroutine through that 45   An operation resumes the awaiting coroutine through that
46   coroutine's executor, not through the executor of the context 46   coroutine's executor, not through the executor of the context
47   that owns the I/O object. 47   that owns the I/O object.
48   48  
49   @note Intended as a protected base class. The handle member 49   @note Intended as a protected base class. The handle member
50   `h_` is accessible to derived classes. 50   `h_` is accessible to derived classes.
51   51  
52   @see @ref io_stream, @ref tcp_socket, @ref tcp_acceptor 52   @see @ref io_stream, @ref tcp_socket, @ref tcp_acceptor
53   */ 53   */
54   class BOOST_COROSIO_DECL io_object 54   class BOOST_COROSIO_DECL io_object
55   { 55   {
56   public: 56   public:
57   class handle; 57   class handle;
58   58  
59   /** Derived types dispatch platform-specific I/O operations through it. 59   /** Derived types dispatch platform-specific I/O operations through it.
60   60  
61   Reference-counted: the owning service holds one reference 61   Reference-counted: the owning service holds one reference
62   (`refs_` starts at 1); in-flight operations hold additional 62   (`refs_` starts at 1); in-flight operations hold additional
63   references through `detail::object_ref`. When the count reaches 63   references through `detail::object_ref`. When the count reaches
64   zero, `retire()` runs — services recycle the impl into 64   zero, `retire()` runs — services recycle the impl into
65   their free-list there. 65   their free-list there.
66   */ 66   */
67   struct implementation 67   struct implementation
68   { 68   {
69   /// Destroy the implementation; called only through @ref io_service. 69   /// Destroy the implementation; called only through @ref io_service.
HITCBC 70   17430 virtual ~implementation() = default; 70   17831 virtual ~implementation() = default;
71   71  
72   /** Retire the implementation once the reference count reaches zero. 72   /** Retire the implementation once the reference count reaches zero.
73   73  
74   Every concrete implementation's override follows the 74   Every concrete implementation's override follows the
75   same shape: recycle into the owning service's pool member 75   same shape: recycle into the owning service's pool member
76   as the final statement. The service befriends the 76   as the final statement. The service befriends the
77   implementation so it can reach that private member 77   implementation so it can reach that private member
78   directly. Any required last-rites cleanup — releasing 78   directly. Any required last-rites cleanup — releasing
79   state that is only safe to tear down once idle — runs as 79   state that is only safe to tear down once idle — runs as
80   ordinary statements immediately before the recycle. 80   ordinary statements immediately before the recycle.
81   Whichever action runs — recycle or delete — must be the 81   Whichever action runs — recycle or delete — must be the
82   final statement: nothing touches the implementation 82   final statement: nothing touches the implementation
83   after. 83   after.
84   */ 84   */
85   virtual void retire() noexcept = 0; 85   virtual void retire() noexcept = 0;
86   86  
87   /// In-flight + service references; starts at the service's 1. 87   /// In-flight + service references; starts at the service's 1.
88   std::atomic<std::size_t> refs_{1}; 88   std::atomic<std::size_t> refs_{1};
89   }; 89   };
90   90  
91   /** Constructs, closes, and destroys platform implementations on 91   /** Constructs, closes, and destroys platform implementations on
92   behalf of an I/O object. Platform backends implement this 92   behalf of an I/O object. Platform backends implement this
93   interface. 93   interface.
94   */ 94   */
95   struct BOOST_COROSIO_DECL io_service 95   struct BOOST_COROSIO_DECL io_service
96   { 96   {
97   /// Destroy the service; the execution context outlives it. 97   /// Destroy the service; the execution context outlives it.
HITCBC 98   5565 virtual ~io_service() = default; 98   5569 virtual ~io_service() = default;
99   99  
100   /** Construct a new implementation instance. 100   /** Construct a new implementation instance.
101   101  
102   May return a recycled implementation taken from the 102   May return a recycled implementation taken from the
103   service's free-list (populated by `implementation::retire`) 103   service's free-list (populated by `implementation::retire`)
104   instead of allocating new storage. 104   instead of allocating new storage.
105   */ 105   */
106   virtual implementation* construct() = 0; 106   virtual implementation* construct() = 0;
107   107  
108   /** Close kernel resources and release the service's reference. 108   /** Close kernel resources and release the service's reference.
109   109  
110   Called whenever a handle relinquishes its current 110   Called whenever a handle relinquishes its current
111   implementation, not only on destruction. Handle 111   implementation, not only on destruction. Handle
112   destruction, move-assignment onto a handle that already 112   destruction, move-assignment onto a handle that already
113   owns one (the replaced implementation is destroyed, the 113   owns one (the replaced implementation is destroyed, the
114   incoming one is not), and `handle::reset()` all invoke 114   incoming one is not), and `handle::reset()` all invoke
115   this. It closes the underlying descriptor and drops the 115   this. It closes the underlying descriptor and drops the
116   service's own reference. In-flight operations may still 116   service's own reference. In-flight operations may still
117   hold references of their own, so the implementation is 117   hold references of their own, so the implementation is
118   not necessarily recycled or freed here. That happens in 118   not necessarily recycled or freed here. That happens in
119   `implementation::retire` whenever the reference count 119   `implementation::retire` whenever the reference count
120   actually reaches zero. 120   actually reaches zero.
121   */ 121   */
122   virtual void destroy(implementation* impl) = 0; 122   virtual void destroy(implementation* impl) = 0;
123   123  
124   /// Close the I/O object, releasing kernel resources without deallocating. 124   /// Close the I/O object, releasing kernel resources without deallocating.
HITCBC 125   23504 virtual void close([[maybe_unused]] handle& h) {} 125   23916 virtual void close([[maybe_unused]] handle& h) {}
126   }; 126   };
127   127  
128   /** Owns a platform-specific I/O implementation and destroys it 128   /** Owns a platform-specific I/O implementation and destroys it
129   when the handle goes out of scope. 129   when the handle goes out of scope.
130   */ 130   */
131   class handle 131   class handle
132   { 132   {
133   capy::execution_context* ctx_ = nullptr; 133   capy::execution_context* ctx_ = nullptr;
134   io_service* svc_ = nullptr; 134   io_service* svc_ = nullptr;
135   implementation* impl_ = nullptr; 135   implementation* impl_ = nullptr;
136   136  
137   public: 137   public:
138   /// Destroy the handle and its implementation. 138   /// Destroy the handle and its implementation.
HITCBC 139   108308 ~handle() 139   109402 ~handle()
140   { 140   {
HITCBC 141   108308 if (impl_) 141   109402 if (impl_)
142   { 142   {
HITCBC 143   47600 svc_->close(*this); 143   48147 svc_->close(*this);
HITCBC 144   47600 svc_->destroy(impl_); 144   48147 svc_->destroy(impl_);
145   } 145   }
HITCBC 146   108308 } 146   109402 }
147   147  
148   /// Construct an empty handle. 148   /// Construct an empty handle.
HITCBC 149   22 handle() = default; 149   22 handle() = default;
150   150  
151   /** Construct a handle that takes over an existing implementation. 151   /** Construct a handle that takes over an existing implementation.
152   152  
153   @pre @p p, if non-null, was constructed by @p svc and is 153   @pre @p p, if non-null, was constructed by @p svc and is
154   owned by no other handle. 154   owned by no other handle.
155   155  
156   @param ctx The context @p svc belongs to. 156   @param ctx The context @p svc belongs to.
157   @param svc The service that constructed @p p. 157   @param svc The service that constructed @p p.
158   @param p The implementation to own. 158   @param p The implementation to own.
159   */ 159   */
HITCBC 160   16 handle( 160   16 handle(
161   capy::execution_context& ctx, 161   capy::execution_context& ctx,
162   io_service& svc, 162   io_service& svc,
163   implementation* p) noexcept 163   implementation* p) noexcept
HITCBC 164   16 : ctx_(&ctx) 164   16 : ctx_(&ctx)
HITCBC 165   16 , svc_(&svc) 165   16 , svc_(&svc)
HITCBC 166   16 , impl_(p) 166   16 , impl_(p)
167   { 167   {
HITCBC 168   16 } 168   16 }
169   169  
170   /// Construct a handle bound to a context and service. 170   /// Construct a handle bound to a context and service.
HITCBC 171   47631 handle(capy::execution_context& ctx, io_service& svc) 171   48178 handle(capy::execution_context& ctx, io_service& svc)
HITCBC 172   47631 : ctx_(&ctx) 172   48178 : ctx_(&ctx)
HITCBC 173   47631 , svc_(&svc) 173   48178 , svc_(&svc)
HITCBC 174   47631 , impl_(svc_->construct()) 174   48178 , impl_(svc_->construct())
175   { 175   {
HITCBC 176   47631 } 176   48178 }
177   177  
178   /// Move construct from another handle. 178   /// Move construct from another handle.
HITCBC 179   60639 handle(handle&& other) noexcept 179   61186 handle(handle&& other) noexcept
HITCBC 180   60639 : ctx_(std::exchange(other.ctx_, nullptr)) 180   61186 : ctx_(std::exchange(other.ctx_, nullptr))
HITCBC 181   60639 , svc_(std::exchange(other.svc_, nullptr)) 181   61186 , svc_(std::exchange(other.svc_, nullptr))
HITCBC 182   60639 , impl_(std::exchange(other.impl_, nullptr)) 182   61186 , impl_(std::exchange(other.impl_, nullptr))
183   { 183   {
HITCBC 184   60639 } 184   61186 }
185   185  
186   /// Move assign from another handle. 186   /// Move assign from another handle.
HITCBC 187   48 handle& operator=(handle&& other) noexcept 187   48 handle& operator=(handle&& other) noexcept
188   { 188   {
HITCBC 189   48 if (this != &other) 189   48 if (this != &other)
190   { 190   {
HITCBC 191   48 if (impl_) 191   48 if (impl_)
192   { 192   {
HITCBC 193   47 svc_->close(*this); 193   47 svc_->close(*this);
HITCBC 194   47 svc_->destroy(impl_); 194   47 svc_->destroy(impl_);
195   } 195   }
HITCBC 196   48 ctx_ = std::exchange(other.ctx_, nullptr); 196   48 ctx_ = std::exchange(other.ctx_, nullptr);
HITCBC 197   48 svc_ = std::exchange(other.svc_, nullptr); 197   48 svc_ = std::exchange(other.svc_, nullptr);
HITCBC 198   48 impl_ = std::exchange(other.impl_, nullptr); 198   48 impl_ = std::exchange(other.impl_, nullptr);
199   } 199   }
HITCBC 200   48 return *this; 200   48 return *this;
201   } 201   }
202   202  
203   /// Copy construction is disabled; the implementation is uniquely owned. 203   /// Copy construction is disabled; the implementation is uniquely owned.
204   handle(handle const&) = delete; 204   handle(handle const&) = delete;
205   /// Copy assignment is disabled; the implementation is uniquely owned. 205   /// Copy assignment is disabled; the implementation is uniquely owned.
206   handle& operator=(handle const&) = delete; 206   handle& operator=(handle const&) = delete;
207   207  
208   /// Return true if the handle owns an implementation. 208   /// Return true if the handle owns an implementation.
HITCBC 209   99909 explicit operator bool() const noexcept 209   100396 explicit operator bool() const noexcept
210   { 210   {
HITCBC 211   99909 return impl_ != nullptr; 211   100396 return impl_ != nullptr;
212   } 212   }
213   213  
214   /// Return the associated I/O service. 214   /// Return the associated I/O service.
HITCBC 215   45281 io_service& service() const noexcept 215   45426 io_service& service() const noexcept
216   { 216   {
HITCBC 217   45281 return *svc_; 217   45426 return *svc_;
218   } 218   }
219   219  
220   /// Return the platform implementation. 220   /// Return the platform implementation.
HITCBC 221   1138887 implementation* get() const noexcept 221   1093106 implementation* get() const noexcept
222   { 222   {
HITCBC 223   1138887 return impl_; 223   1093106 return impl_;
224   } 224   }
225   225  
226   /** Replace the implementation, destroying the old one. 226   /** Replace the implementation, destroying the old one.
227   227  
228   @pre The handle is already bound to a service (constructed 228   @pre The handle is already bound to a service (constructed
229   via the service-taking constructor, not the default 229   via the service-taking constructor, not the default
230   one) — the old implementation is destroyed through 230   one) — the old implementation is destroyed through
231   that service. 231   that service.
232   @pre @p p, if non-null, was constructed by this handle's 232   @pre @p p, if non-null, was constructed by this handle's
233   own service. The service that destroys it later must 233   own service. The service that destroys it later must
234   be the one that knows how to close it. 234   be the one that knows how to close it.
235   235  
236   @param p The new implementation to own. May be nullptr. 236   @param p The new implementation to own. May be nullptr.
237   */ 237   */
HITCBC 238   6476 void reset(implementation* p) noexcept 238   6549 void reset(implementation* p) noexcept
239   { 239   {
HITCBC 240   6476 if (impl_) 240   6549 if (impl_)
241   { 241   {
HITCBC 242   6476 svc_->close(*this); 242   6549 svc_->close(*this);
HITCBC 243   6476 svc_->destroy(impl_); 243   6549 svc_->destroy(impl_);
244   } 244   }
HITCBC 245   6476 impl_ = p; 245   6549 impl_ = p;
HITCBC 246   6476 } 246   6549 }
247   247  
248   /// Return the execution context. 248   /// Return the execution context.
HITCBC 249   37 capy::execution_context& context() const noexcept 249   37 capy::execution_context& context() const noexcept
250   { 250   {
HITCBC 251   37 return *ctx_; 251   37 return *ctx_;
252   } 252   }
253   }; 253   };
254   254  
255   /// Return the execution context. 255   /// Return the execution context.
HITCBC 256   37 capy::execution_context& context() const noexcept 256   37 capy::execution_context& context() const noexcept
257   { 257   {
HITCBC 258   37 return h_.context(); 258   37 return h_.context();
259   } 259   }
260   260  
261   protected: 261   protected:
262   /// Destroy the object; protected, so only a derived type destroys one. 262   /// Destroy the object; protected, so only a derived type destroys one.
HITCBC 263   57002 virtual ~io_object() = default; 263   57549 virtual ~io_object() = default;
264   264  
265   /// Default construct for virtual base initialization. 265   /// Default construct for virtual base initialization.
HITCBC 266   22 io_object() noexcept = default; 266   22 io_object() noexcept = default;
267   267  
268   /** Create a handle bound to a service found in the context. 268   /** Create a handle bound to a service found in the context.
269   269  
270   @tparam Service The service type whose `key_type` is used for 270   @tparam Service The service type whose `key_type` is used for
271   lookup. 271   lookup.
272   @param ctx The execution context to search for the service. 272   @param ctx The execution context to search for the service.
273   273  
274   @return A handle owning a freshly constructed implementation. 274   @return A handle owning a freshly constructed implementation.
275   275  
276   @throws std::logic_error if the service is not installed. 276   @throws std::logic_error if the service is not installed.
277   */ 277   */
278   template<class Service> 278   template<class Service>
279   static handle create_handle(capy::execution_context& ctx) 279   static handle create_handle(capy::execution_context& ctx)
280   { 280   {
281   auto* svc = ctx.find_service<Service>(); 281   auto* svc = ctx.find_service<Service>();
282   if (!svc) 282   if (!svc)
283   detail::throw_logic_error( 283   detail::throw_logic_error(
284   "io_object::create_handle: service not installed"); 284   "io_object::create_handle: service not installed");
285   return handle(ctx, *svc); 285   return handle(ctx, *svc);
286   } 286   }
287   287  
288   /// Construct an I/O object from a handle. 288   /// Construct an I/O object from a handle.
HITCBC 289   47631 explicit io_object(handle h) noexcept : h_(std::move(h)) {} 289   48178 explicit io_object(handle h) noexcept : h_(std::move(h)) {}
290   290  
291   /// Move construct from another I/O object. 291   /// Move construct from another I/O object.
HITCBC 292   9349 io_object(io_object&& other) noexcept : h_(std::move(other.h_)) {} 292   9349 io_object(io_object&& other) noexcept : h_(std::move(other.h_)) {}
293   293  
294   /// Move assign from another I/O object. 294   /// Move assign from another I/O object.
HITCBC 295   4 io_object& operator=(io_object&& other) noexcept 295   4 io_object& operator=(io_object&& other) noexcept
296   { 296   {
HITCBC 297   4 if (this != &other) 297   4 if (this != &other)
HITCBC 298   4 h_ = std::move(other.h_); 298   4 h_ = std::move(other.h_);
HITCBC 299   4 return *this; 299   4 return *this;
300   } 300   }
301   301  
302   /// Copy construction is disabled; the handle is uniquely owned. 302   /// Copy construction is disabled; the handle is uniquely owned.
303   io_object(io_object const&) = delete; 303   io_object(io_object const&) = delete;
304   /// Copy assignment is disabled; the handle is uniquely owned. 304   /// Copy assignment is disabled; the handle is uniquely owned.
305   io_object& operator=(io_object const&) = delete; 305   io_object& operator=(io_object const&) = delete;
306   306  
307   /// The platform I/O handle owned by this object. 307   /// The platform I/O handle owned by this object.
308   BOOST_COROSIO_MSVC_WARNING_PUSH 308   BOOST_COROSIO_MSVC_WARNING_PUSH
309   BOOST_COROSIO_MSVC_WARNING_DISABLE(4251) 309   BOOST_COROSIO_MSVC_WARNING_DISABLE(4251)
310   handle h_; 310   handle h_;
311   BOOST_COROSIO_MSVC_WARNING_POP 311   BOOST_COROSIO_MSVC_WARNING_POP
312   }; 312   };
313   313  
314   } // namespace boost::corosio 314   } // namespace boost::corosio
315   315  
316   #endif 316   #endif