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