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