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