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