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