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
  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 +
44   @note Intended as a protected base class. The handle member 49   @note Intended as a protected base class. The handle member
45   `h_` is accessible to derived classes. 50   `h_` is accessible to derived classes.
46   51  
47   @see @ref io_stream, @ref tcp_socket, @ref tcp_acceptor 52   @see @ref io_stream, @ref tcp_socket, @ref tcp_acceptor
48   */ 53   */
49   class BOOST_COROSIO_DECL io_object 54   class BOOST_COROSIO_DECL io_object
50   { 55   {
51   public: 56   public:
52   class handle; 57   class handle;
53   58  
54   /** Derived types dispatch platform-specific I/O operations through it. 59   /** Derived types dispatch platform-specific I/O operations through it.
55   60  
56   Reference-counted: the owning service holds one reference 61   Reference-counted: the owning service holds one reference
57   (`refs_` starts at 1); in-flight operations hold additional 62   (`refs_` starts at 1); in-flight operations hold additional
58   references through `detail::object_ref`. When the count reaches 63   references through `detail::object_ref`. When the count reaches
59   zero, `retire()` runs — services recycle the impl into 64   zero, `retire()` runs — services recycle the impl into
60   their free-list there. 65   their free-list there.
61   */ 66   */
62   struct implementation 67   struct implementation
63   { 68   {
64   /// Destroy the implementation; called only through @ref io_service. 69   /// Destroy the implementation; called only through @ref io_service.
HITCBC 65   17526 virtual ~implementation() = default; 70   18158 virtual ~implementation() = default;
66   71  
67   /** Retire the implementation once the reference count reaches zero. 72   /** Retire the implementation once the reference count reaches zero.
68   73  
69   Every concrete implementation's override follows the 74   Every concrete implementation's override follows the
70   same shape: recycle into the owning service's pool member 75   same shape: recycle into the owning service's pool member
71   as the final statement. The service befriends the 76   as the final statement. The service befriends the
72   implementation so it can reach that private member 77   implementation so it can reach that private member
73   directly. Any required last-rites cleanup — releasing 78   directly. Any required last-rites cleanup — releasing
74   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
75   ordinary statements immediately before the recycle. 80   ordinary statements immediately before the recycle.
76   Whichever action runs — recycle or delete — must be the 81   Whichever action runs — recycle or delete — must be the
77   final statement: nothing touches the implementation 82   final statement: nothing touches the implementation
78   after. 83   after.
79   */ 84   */
80   virtual void retire() noexcept = 0; 85   virtual void retire() noexcept = 0;
81   86  
82   /// In-flight + service references; starts at the service's 1. 87   /// In-flight + service references; starts at the service's 1.
83   std::atomic<std::size_t> refs_{1}; 88   std::atomic<std::size_t> refs_{1};
84   }; 89   };
85   90  
86   /** Constructs, closes, and destroys platform implementations on 91   /** Constructs, closes, and destroys platform implementations on
87   behalf of an I/O object. Platform backends implement this 92   behalf of an I/O object. Platform backends implement this
88   interface. 93   interface.
89   */ 94   */
90   struct BOOST_COROSIO_DECL io_service 95   struct BOOST_COROSIO_DECL io_service
91   { 96   {
92   /// Destroy the service; the execution context outlives it. 97   /// Destroy the service; the execution context outlives it.
HITCBC 93   5558 virtual ~io_service() = default; 98   5575 virtual ~io_service() = default;
94   99  
95   /** Construct a new implementation instance. 100   /** Construct a new implementation instance.
96   101  
97   May return a recycled implementation taken from the 102   May return a recycled implementation taken from the
98   service's free-list (populated by `implementation::retire`) 103   service's free-list (populated by `implementation::retire`)
99   instead of allocating new storage. 104   instead of allocating new storage.
100   */ 105   */
101   virtual implementation* construct() = 0; 106   virtual implementation* construct() = 0;
102   107  
103   /** Close kernel resources and release the service's reference. 108   /** Close kernel resources and release the service's reference.
104   109  
105   Called whenever a handle relinquishes its current 110   Called whenever a handle relinquishes its current
106   implementation, not only on destruction. Handle 111   implementation, not only on destruction. Handle
107   destruction, move-assignment onto a handle that already 112   destruction, move-assignment onto a handle that already
108   owns one (the replaced implementation is destroyed, the 113   owns one (the replaced implementation is destroyed, the
109   incoming one is not), and `handle::reset()` all invoke 114   incoming one is not), and `handle::reset()` all invoke
110   this. It closes the underlying descriptor and drops the 115   this. It closes the underlying descriptor and drops the
111   service's own reference. In-flight operations may still 116   service's own reference. In-flight operations may still
112   hold references of their own, so the implementation is 117   hold references of their own, so the implementation is
113   not necessarily recycled or freed here. That happens in 118   not necessarily recycled or freed here. That happens in
114   `implementation::retire` whenever the reference count 119   `implementation::retire` whenever the reference count
115   actually reaches zero. 120   actually reaches zero.
116   */ 121   */
117   virtual void destroy(implementation* impl) = 0; 122   virtual void destroy(implementation* impl) = 0;
118   123  
119   /// Close the I/O object, releasing kernel resources without deallocating. 124   /// Close the I/O object, releasing kernel resources without deallocating.
HITCBC 120   23526 virtual void close([[maybe_unused]] handle& h) {} 125   24179 virtual void close([[maybe_unused]] handle& h) {}
121   }; 126   };
122   127  
123   /** Owns a platform-specific I/O implementation and destroys it 128   /** Owns a platform-specific I/O implementation and destroys it
124   when the handle goes out of scope. 129   when the handle goes out of scope.
125   */ 130   */
126   class handle 131   class handle
127   { 132   {
128   capy::execution_context* ctx_ = nullptr; 133   capy::execution_context* ctx_ = nullptr;
129   io_service* svc_ = nullptr; 134   io_service* svc_ = nullptr;
130   implementation* impl_ = nullptr; 135   implementation* impl_ = nullptr;
131   136  
132   public: 137   public:
133   /// Destroy the handle and its implementation. 138   /// Destroy the handle and its implementation.
HITCBC 134   108132 ~handle() 139   110412 ~handle()
135   { 140   {
HITCBC 136   108132 if (impl_) 141   110412 if (impl_)
137   { 142   {
HITCBC 138   47512 svc_->close(*this); 143   48656 svc_->close(*this);
HITCBC 139   47512 svc_->destroy(impl_); 144   48656 svc_->destroy(impl_);
140   } 145   }
HITCBC 141   108132 } 146   110412 }
142   147  
143   /// Construct an empty handle. 148   /// Construct an empty handle.
HITCBC 144   22 handle() = default; 149   22 handle() = default;
145   150  
146   /** Construct a handle that takes over an existing implementation. 151   /** Construct a handle that takes over an existing implementation.
147   152  
148   @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
149   owned by no other handle. 154   owned by no other handle.
150   155  
151   @param ctx The context @p svc belongs to. 156   @param ctx The context @p svc belongs to.
152   @param svc The service that constructed @p p. 157   @param svc The service that constructed @p p.
153   @param p The implementation to own. 158   @param p The implementation to own.
154   */ 159   */
HITCBC 155   16 handle( 160   16 handle(
156   capy::execution_context& ctx, 161   capy::execution_context& ctx,
157   io_service& svc, 162   io_service& svc,
158   implementation* p) noexcept 163   implementation* p) noexcept
HITCBC 159   16 : ctx_(&ctx) 164   16 : ctx_(&ctx)
HITCBC 160   16 , svc_(&svc) 165   16 , svc_(&svc)
HITCBC 161   16 , impl_(p) 166   16 , impl_(p)
162   { 167   {
HITCBC 163   16 } 168   16 }
164   169  
165   /// Construct a handle bound to a context and service. 170   /// Construct a handle bound to a context and service.
HITCBC 166   47543 handle(capy::execution_context& ctx, io_service& svc) 171   48687 handle(capy::execution_context& ctx, io_service& svc)
HITCBC 167   47543 : ctx_(&ctx) 172   48687 : ctx_(&ctx)
HITCBC 168   47543 , svc_(&svc) 173   48687 , svc_(&svc)
HITCBC 169   47543 , impl_(svc_->construct()) 174   48687 , impl_(svc_->construct())
170   { 175   {
HITCBC 171   47543 } 176   48687 }
172   177  
173   /// Move construct from another handle. 178   /// Move construct from another handle.
HITCBC 174   60551 handle(handle&& other) noexcept 179   61687 handle(handle&& other) noexcept
HITCBC 175   60551 : ctx_(std::exchange(other.ctx_, nullptr)) 180   61687 : ctx_(std::exchange(other.ctx_, nullptr))
HITCBC 176   60551 , svc_(std::exchange(other.svc_, nullptr)) 181   61687 , svc_(std::exchange(other.svc_, nullptr))
HITCBC 177   60551 , impl_(std::exchange(other.impl_, nullptr)) 182   61687 , impl_(std::exchange(other.impl_, nullptr))
178   { 183   {
HITCBC 179   60551 } 184   61687 }
180   185  
181   /// Move assign from another handle. 186   /// Move assign from another handle.
HITCBC 182   48 handle& operator=(handle&& other) noexcept 187   48 handle& operator=(handle&& other) noexcept
183   { 188   {
HITCBC 184   48 if (this != &other) 189   48 if (this != &other)
185   { 190   {
HITCBC 186   48 if (impl_) 191   48 if (impl_)
187   { 192   {
HITCBC 188   47 svc_->close(*this); 193   47 svc_->close(*this);
HITCBC 189   47 svc_->destroy(impl_); 194   47 svc_->destroy(impl_);
190   } 195   }
HITCBC 191   48 ctx_ = std::exchange(other.ctx_, nullptr); 196   48 ctx_ = std::exchange(other.ctx_, nullptr);
HITCBC 192   48 svc_ = std::exchange(other.svc_, nullptr); 197   48 svc_ = std::exchange(other.svc_, nullptr);
HITCBC 193   48 impl_ = std::exchange(other.impl_, nullptr); 198   48 impl_ = std::exchange(other.impl_, nullptr);
194   } 199   }
HITCBC 195   48 return *this; 200   48 return *this;
196   } 201   }
197   202  
198   /// Copy construction is disabled; the implementation is uniquely owned. 203   /// Copy construction is disabled; the implementation is uniquely owned.
199   handle(handle const&) = delete; 204   handle(handle const&) = delete;
200   /// Copy assignment is disabled; the implementation is uniquely owned. 205   /// Copy assignment is disabled; the implementation is uniquely owned.
201   handle& operator=(handle const&) = delete; 206   handle& operator=(handle const&) = delete;
202   207  
203   /// Return true if the handle owns an implementation. 208   /// Return true if the handle owns an implementation.
HITCBC 204   99190 explicit operator bool() const noexcept 209   101186 explicit operator bool() const noexcept
205   { 210   {
HITCBC 206   99190 return impl_ != nullptr; 211   101186 return impl_ != nullptr;
207   } 212   }
208   213  
209   /// Return the associated I/O service. 214   /// Return the associated I/O service.
HITCBC 210   44914 io_service& service() const noexcept 215   45940 io_service& service() const noexcept
211   { 216   {
HITCBC 212   44914 return *svc_; 217   45940 return *svc_;
213   } 218   }
214   219  
215   /// Return the platform implementation. 220   /// Return the platform implementation.
HITCBC 216   1105933 implementation* get() const noexcept 221   1113986 implementation* get() const noexcept
217   { 222   {
HITCBC 218   1105933 return impl_; 223   1113986 return impl_;
219   } 224   }
220   225  
221   /** Replace the implementation, destroying the old one. 226   /** Replace the implementation, destroying the old one.
222   227  
223   @pre The handle is already bound to a service (constructed 228   @pre The handle is already bound to a service (constructed
224   via the service-taking constructor, not the default 229   via the service-taking constructor, not the default
225   one) — the old implementation is destroyed through 230   one) — the old implementation is destroyed through
226   that service. 231   that service.
227   @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
228   own service. The service that destroys it later must 233   own service. The service that destroys it later must
229   be the one that knows how to close it. 234   be the one that knows how to close it.
230   235  
231   @param p The new implementation to own. May be nullptr. 236   @param p The new implementation to own. May be nullptr.
232   */ 237   */
HITCBC 233   6337 void reset(implementation* p) noexcept 238   6341 void reset(implementation* p) noexcept
234   { 239   {
HITCBC 235   6337 if (impl_) 240   6341 if (impl_)
236   { 241   {
HITCBC 237   6337 svc_->close(*this); 242   6341 svc_->close(*this);
HITCBC 238   6337 svc_->destroy(impl_); 243   6341 svc_->destroy(impl_);
239   } 244   }
HITCBC 240   6337 impl_ = p; 245   6341 impl_ = p;
HITCBC 241   6337 } 246   6341 }
242   247  
243   /// Return the execution context. 248   /// Return the execution context.
HITCBC 244   37 capy::execution_context& context() const noexcept 249   37 capy::execution_context& context() const noexcept
245   { 250   {
HITCBC 246   37 return *ctx_; 251   37 return *ctx_;
247   } 252   }
248   }; 253   };
249   254  
250   /// Return the execution context. 255   /// Return the execution context.
HITCBC 251   37 capy::execution_context& context() const noexcept 256   37 capy::execution_context& context() const noexcept
252   { 257   {
HITCBC 253   37 return h_.context(); 258   37 return h_.context();
254   } 259   }
255   260  
256   protected: 261   protected:
257   /// Destroy the object; protected, so only a derived type destroys one. 262   /// Destroy the object; protected, so only a derived type destroys one.
HITCBC 258   56914 virtual ~io_object() = default; 263   58050 virtual ~io_object() = default;
259   264  
260   /// Default construct for virtual base initialization. 265   /// Default construct for virtual base initialization.
HITCBC 261   22 io_object() noexcept = default; 266   22 io_object() noexcept = default;
262   267  
263   /** Create a handle bound to a service found in the context. 268   /** Create a handle bound to a service found in the context.
264   269  
265   @tparam Service The service type whose `key_type` is used for 270   @tparam Service The service type whose `key_type` is used for
266   lookup. 271   lookup.
267   @param ctx The execution context to search for the service. 272   @param ctx The execution context to search for the service.
268   273  
269   @return A handle owning a freshly constructed implementation. 274   @return A handle owning a freshly constructed implementation.
270   275  
271   @throws std::logic_error if the service is not installed. 276   @throws std::logic_error if the service is not installed.
272   */ 277   */
273   template<class Service> 278   template<class Service>
274   static handle create_handle(capy::execution_context& ctx) 279   static handle create_handle(capy::execution_context& ctx)
275   { 280   {
276   auto* svc = ctx.find_service<Service>(); 281   auto* svc = ctx.find_service<Service>();
277   if (!svc) 282   if (!svc)
278   detail::throw_logic_error( 283   detail::throw_logic_error(
279   "io_object::create_handle: service not installed"); 284   "io_object::create_handle: service not installed");
280   return handle(ctx, *svc); 285   return handle(ctx, *svc);
281   } 286   }
282   287  
283   /// Construct an I/O object from a handle. 288   /// Construct an I/O object from a handle.
HITCBC 284   47543 explicit io_object(handle h) noexcept : h_(std::move(h)) {} 289   48687 explicit io_object(handle h) noexcept : h_(std::move(h)) {}
285   290  
286   /// Move construct from another I/O object. 291   /// Move construct from another I/O object.
HITCBC 287   9349 io_object(io_object&& other) noexcept : h_(std::move(other.h_)) {} 292   9341 io_object(io_object&& other) noexcept : h_(std::move(other.h_)) {}
288   293  
289   /// Move assign from another I/O object. 294   /// Move assign from another I/O object.
HITCBC 290   4 io_object& operator=(io_object&& other) noexcept 295   4 io_object& operator=(io_object&& other) noexcept
291   { 296   {
HITCBC 292   4 if (this != &other) 297   4 if (this != &other)
HITCBC 293   4 h_ = std::move(other.h_); 298   4 h_ = std::move(other.h_);
HITCBC 294   4 return *this; 299   4 return *this;
295   } 300   }
296   301  
297   /// Copy construction is disabled; the handle is uniquely owned. 302   /// Copy construction is disabled; the handle is uniquely owned.
298   io_object(io_object const&) = delete; 303   io_object(io_object const&) = delete;
299   /// Copy assignment is disabled; the handle is uniquely owned. 304   /// Copy assignment is disabled; the handle is uniquely owned.
300   io_object& operator=(io_object const&) = delete; 305   io_object& operator=(io_object const&) = delete;
301   306  
302   /// The platform I/O handle owned by this object. 307   /// The platform I/O handle owned by this object.
303   BOOST_COROSIO_MSVC_WARNING_PUSH 308   BOOST_COROSIO_MSVC_WARNING_PUSH
304   BOOST_COROSIO_MSVC_WARNING_DISABLE(4251) 309   BOOST_COROSIO_MSVC_WARNING_DISABLE(4251)
305   handle h_; 310   handle h_;
306   BOOST_COROSIO_MSVC_WARNING_POP 311   BOOST_COROSIO_MSVC_WARNING_POP
307   }; 312   };
308   313  
309   } // namespace boost::corosio 314   } // namespace boost::corosio
310   315  
311   #endif 316   #endif