100.00% Lines (75/75) 100.00% Functions (16/16)
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
3   // 4   //
4   // Distributed under the Boost Software License, Version 1.0. (See accompanying 5   // Distributed under the Boost Software License, Version 1.0. (See accompanying
5   // 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)
6   // 7   //
7   // Official repository: https://github.com/cppalliance/capy 8   // Official repository: https://github.com/cppalliance/capy
8   // 9   //
9   10  
10   #ifndef BOOST_CAPY_IO_ANY_READ_STREAM_HPP 11   #ifndef BOOST_CAPY_IO_ANY_READ_STREAM_HPP
11   #define BOOST_CAPY_IO_ANY_READ_STREAM_HPP 12   #define BOOST_CAPY_IO_ANY_READ_STREAM_HPP
12   13  
13   #include <boost/capy/detail/config.hpp> 14   #include <boost/capy/detail/config.hpp>
14   #include <boost/capy/detail/await_suspend_helper.hpp> 15   #include <boost/capy/detail/await_suspend_helper.hpp>
15   #include <boost/capy/buffers.hpp> 16   #include <boost/capy/buffers.hpp>
16   #include <boost/capy/detail/buffer_array.hpp> 17   #include <boost/capy/detail/buffer_array.hpp>
17   #include <boost/capy/concept/io_awaitable.hpp> 18   #include <boost/capy/concept/io_awaitable.hpp>
18   #include <boost/capy/concept/read_stream.hpp> 19   #include <boost/capy/concept/read_stream.hpp>
19   #include <boost/capy/ex/io_env.hpp> 20   #include <boost/capy/ex/io_env.hpp>
20   #include <boost/capy/io_result.hpp> 21   #include <boost/capy/io_result.hpp>
21   22  
22   #include <concepts> 23   #include <concepts>
23   #include <coroutine> 24   #include <coroutine>
24   #include <cstddef> 25   #include <cstddef>
25   #include <exception> 26   #include <exception>
26   #include <new> 27   #include <new>
27   #include <span> 28   #include <span>
28   #include <stop_token> 29   #include <stop_token>
29   #include <system_error> 30   #include <system_error>
30   #include <utility> 31   #include <utility>
31   32  
32   namespace boost { 33   namespace boost {
33   namespace capy { 34   namespace capy {
34   35  
35 - /** Type-erased wrapper for any ReadStream. 36 + /** Dispatches `read_some` through a type-erased vtable, using preallocated awaitable storage.
36   37  
37   This class provides type erasure for any type satisfying the 38   This class provides type erasure for any type satisfying the
38   @ref ReadStream concept, enabling runtime polymorphism for 39   @ref ReadStream concept, enabling runtime polymorphism for
39   read operations. It uses cached awaitable storage to achieve 40   read operations. It uses cached awaitable storage to achieve
40   zero steady-state allocation after construction. 41   zero steady-state allocation after construction.
41   42  
42   The wrapper supports two construction modes: 43   The wrapper supports two construction modes:
43   - **Owning**: Pass by value to transfer ownership. The wrapper 44   - **Owning**: Pass by value to transfer ownership. The wrapper
44   allocates storage and owns the stream. 45   allocates storage and owns the stream.
45   - **Reference**: Pass a pointer to wrap without ownership. The 46   - **Reference**: Pass a pointer to wrap without ownership. The
46   pointed-to stream must outlive this wrapper. 47   pointed-to stream must outlive this wrapper.
47   48  
48   @par Awaitable Preallocation 49   @par Awaitable Preallocation
49   The constructor preallocates storage for the type-erased awaitable. 50   The constructor preallocates storage for the type-erased awaitable.
50   This reserves all virtual address space at server startup 51   This reserves all virtual address space at server startup
51   so memory usage can be measured up front, rather than 52   so memory usage can be measured up front, rather than
52   allocating piecemeal as traffic arrives. 53   allocating piecemeal as traffic arrives.
53   54  
54   @par Immediate Completion 55   @par Immediate Completion
55   When the underlying stream's awaitable reports ready immediately 56   When the underlying stream's awaitable reports ready immediately
56   (e.g. buffered data already available), the wrapper skips 57   (e.g. buffered data already available), the wrapper skips
57   coroutine suspension entirely and returns the result inline. 58   coroutine suspension entirely and returns the result inline.
58   59  
59   @par Thread Safety 60   @par Thread Safety
60   Not thread-safe. Concurrent operations on the same wrapper 61   Not thread-safe. Concurrent operations on the same wrapper
61   are undefined behavior. 62   are undefined behavior.
62   63  
63   @par Example 64   @par Example
64   @code 65   @code
65   // Owning - takes ownership of the stream 66   // Owning - takes ownership of the stream
66 - any_read_stream stream(socket{ioc}); 67 + any_read_stream owning_stream(socket{ioc});
67   68  
68   // Reference - wraps without ownership 69   // Reference - wraps without ownership
69   socket sock(ioc); 70   socket sock(ioc);
70 - any_read_stream stream(&sock); 71 + any_read_stream ref_stream(&sock);
71   72  
72 - mutable_buffer buf(data, size); 73 + char data[1024];
73 - auto [ec, n] = co_await stream.read_some(buf); 74 + mutable_buffer buf(data, sizeof(data));
  75 + auto [ec, n] = co_await owning_stream.read_some(buf);
74   @endcode 76   @endcode
75   77  
76   @see any_write_stream, any_stream, ReadStream 78   @see any_write_stream, any_stream, ReadStream
77   */ 79   */
78   class any_read_stream 80   class any_read_stream
79   { 81   {
80   struct vtable; 82   struct vtable;
81   83  
82   template<ReadStream S> 84   template<ReadStream S>
83   struct vtable_for_impl; 85   struct vtable_for_impl;
84   86  
85   // ordered for cache line coherence 87   // ordered for cache line coherence
86   void* stream_ = nullptr; 88   void* stream_ = nullptr;
87   vtable const* vt_ = nullptr; 89   vtable const* vt_ = nullptr;
88   void* cached_awaitable_ = nullptr; 90   void* cached_awaitable_ = nullptr;
89   void* storage_ = nullptr; 91   void* storage_ = nullptr;
90   bool awaitable_active_ = false; 92   bool awaitable_active_ = false;
91   93  
92   public: 94   public:
93   /** Destructor. 95   /** Destructor.
94   96  
95   Destroys the owned stream (if any) and releases the cached 97   Destroys the owned stream (if any) and releases the cached
96   awaitable storage. 98   awaitable storage.
97   */ 99   */
98   ~any_read_stream(); 100   ~any_read_stream();
99   101  
100   /** Construct a default instance. 102   /** Construct a default instance.
101   103  
102 - Constructs an empty wrapper. Operations on a default-constructed 104 + Constructs an empty wrapper. @ref has_value and `operator bool`
103 - wrapper result in undefined behavior. 105 + report the empty state; calling @ref read_some before the
  106 + wrapper holds a stream is undefined behavior.
104   */ 107   */
HITCBC 105   4 any_read_stream() = default; 108   4 any_read_stream() = default;
106   109  
107   /** Non-copyable. 110   /** Non-copyable.
108   111  
109   The awaitable cache is per-instance and cannot be shared. 112   The awaitable cache is per-instance and cannot be shared.
  113 +
  114 + @param other The wrapper that would be copied.
110   */ 115   */
111 - any_read_stream(any_read_stream const&) = delete; 116 + any_read_stream(any_read_stream const& other) = delete;
112 - any_read_stream& operator=(any_read_stream const&) = delete; 117 +
  118 + /** Copy assignment is disabled.
  119 +
  120 + The awaitable cache is per-instance and cannot be shared.
  121 +
  122 + @param other The wrapper that would be assigned from.
  123 +
  124 + @return A reference to `*this`.
  125 + */
  126 + any_read_stream& operator=(any_read_stream const& other) = delete;
113   127  
114   /** Construct by moving. 128   /** Construct by moving.
115   129  
116   Transfers ownership of the wrapped stream (if owned) and 130   Transfers ownership of the wrapped stream (if owned) and
117   cached awaitable storage from `other`. After the move, `other` is 131   cached awaitable storage from `other`. After the move, `other` is
118   in a default-constructed state. 132   in a default-constructed state.
119   133  
120   @param other The wrapper to move from. 134   @param other The wrapper to move from.
121   */ 135   */
HITCBC 122   4 any_read_stream(any_read_stream&& other) noexcept 136   4 any_read_stream(any_read_stream&& other) noexcept
HITCBC 123   4 : stream_(std::exchange(other.stream_, nullptr)) 137   4 : stream_(std::exchange(other.stream_, nullptr))
HITCBC 124   4 , vt_(std::exchange(other.vt_, nullptr)) 138   4 , vt_(std::exchange(other.vt_, nullptr))
HITCBC 125   4 , cached_awaitable_(std::exchange(other.cached_awaitable_, nullptr)) 139   4 , cached_awaitable_(std::exchange(other.cached_awaitable_, nullptr))
HITCBC 126   4 , storage_(std::exchange(other.storage_, nullptr)) 140   4 , storage_(std::exchange(other.storage_, nullptr))
HITCBC 127   4 , awaitable_active_(std::exchange(other.awaitable_active_, false)) 141   4 , awaitable_active_(std::exchange(other.awaitable_active_, false))
128   { 142   {
HITCBC 129   4 } 143   4 }
130   144  
131   /** Assign by moving. 145   /** Assign by moving.
132   146  
133   Destroys any owned stream and releases existing resources, 147   Destroys any owned stream and releases existing resources,
134   then transfers ownership from `other`. 148   then transfers ownership from `other`.
135   149  
136   @param other The wrapper to move from. 150   @param other The wrapper to move from.
137   @return Reference to this wrapper. 151   @return Reference to this wrapper.
138   */ 152   */
139   any_read_stream& 153   any_read_stream&
140   operator=(any_read_stream&& other) noexcept; 154   operator=(any_read_stream&& other) noexcept;
141   155  
142   /** Construct by taking ownership of a ReadStream. 156   /** Construct by taking ownership of a ReadStream.
143   157  
144   Allocates storage and moves the stream into this wrapper. 158   Allocates storage and moves the stream into this wrapper.
145 - The wrapper owns the stream and will destroy it. 159 + The wrapper owns the stream and destroys it.
146   160  
147   @param s The stream to take ownership of. 161   @param s The stream to take ownership of.
148   */ 162   */
149   template<ReadStream S> 163   template<ReadStream S>
150   requires (!std::same_as<std::decay_t<S>, any_read_stream>) 164   requires (!std::same_as<std::decay_t<S>, any_read_stream>)
151   any_read_stream(S s); 165   any_read_stream(S s);
152   166  
153   /** Construct by wrapping a ReadStream without ownership. 167   /** Construct by wrapping a ReadStream without ownership.
154   168  
155   Wraps the given stream by pointer. The stream must remain 169   Wraps the given stream by pointer. The stream must remain
156   valid for the lifetime of this wrapper. 170   valid for the lifetime of this wrapper.
157   171  
158   @param s Pointer to the stream to wrap. 172   @param s Pointer to the stream to wrap.
159   */ 173   */
160   template<ReadStream S> 174   template<ReadStream S>
161   any_read_stream(S* s); 175   any_read_stream(S* s);
162   176  
163   /** Check if the wrapper contains a valid stream. 177   /** Check if the wrapper contains a valid stream.
164   178  
165   @return `true` if wrapping a stream, `false` if default-constructed 179   @return `true` if wrapping a stream, `false` if default-constructed
166   or moved-from. 180   or moved-from.
167   */ 181   */
168   bool 182   bool
HITCBC 169   31 has_value() const noexcept 183   31 has_value() const noexcept
170   { 184   {
HITCBC 171   31 return stream_ != nullptr; 185   31 return stream_ != nullptr;
172   } 186   }
173   187  
174   /** Check if the wrapper contains a valid stream. 188   /** Check if the wrapper contains a valid stream.
175   189  
176   @return `true` if wrapping a stream, `false` if default-constructed 190   @return `true` if wrapping a stream, `false` if default-constructed
177   or moved-from. 191   or moved-from.
178   */ 192   */
179   explicit 193   explicit
HITCBC 180   3 operator bool() const noexcept 194   3 operator bool() const noexcept
181   { 195   {
HITCBC 182   3 return has_value(); 196   3 return has_value();
183   } 197   }
184   198  
185   /** Initiate an asynchronous read operation. 199   /** Initiate an asynchronous read operation.
186   200  
187   Reads data into the provided buffer sequence. The operation 201   Reads data into the provided buffer sequence. The operation
188 - completes when at least one byte has been read, or an error 202 + completes when at least one byte is read, or an error
189   occurs. 203   occurs.
190   204  
191   @param buffers The buffer sequence to read into. Passed by 205   @param buffers The buffer sequence to read into. Passed by
192   value to ensure the sequence lives in the coroutine frame 206   value to ensure the sequence lives in the coroutine frame
193   across suspension points. 207   across suspension points.
194   208  
195   @return An awaitable that await-returns `(error_code,std::size_t)`. 209   @return An awaitable that await-returns `(error_code,std::size_t)`.
196   210  
197   @par Immediate Completion 211   @par Immediate Completion
198   The operation completes immediately without suspending 212   The operation completes immediately without suspending
199   the calling coroutine when the underlying stream's 213   the calling coroutine when the underlying stream's
200   awaitable reports immediate readiness via `await_ready`. 214   awaitable reports immediate readiness via `await_ready`.
201   215  
202   @note This is a partial operation and may not process the 216   @note This is a partial operation and may not process the
203   entire buffer sequence. Use the composed @ref read algorithm 217   entire buffer sequence. Use the composed @ref read algorithm
204   for guaranteed complete transfer. 218   for guaranteed complete transfer.
205   219  
206   @par Preconditions 220   @par Preconditions
207   The wrapper must contain a valid stream (`has_value() == true`). 221   The wrapper must contain a valid stream (`has_value() == true`).
208 - The caller must not call this function again after a prior 222 +
209 - call returned an error (including EOF). 223 + @par After an Error
  224 + A subsequent call is permitted. The wrapper forwards directly
  225 + to the underlying stream, imposing no stricter rule than
  226 + @ref ReadStream.
210   */ 227   */
211   template<MutableBufferSequence MB> 228   template<MutableBufferSequence MB>
212   auto 229   auto
213   read_some(MB buffers); 230   read_some(MB buffers);
214   231  
215   protected: 232   protected:
216   /** Rebind to a new stream after move. 233   /** Rebind to a new stream after move.
217   234  
218   Updates the internal pointer to reference a new stream object. 235   Updates the internal pointer to reference a new stream object.
219   Used by owning wrappers after move assignment when the owned 236   Used by owning wrappers after move assignment when the owned
220   object has moved to a new location. 237   object has moved to a new location.
221   238  
222   @param new_stream The new stream to bind to. Must be the same 239   @param new_stream The new stream to bind to. Must be the same
223   type as the original stream. 240   type as the original stream.
224   241  
225   @note Terminates if called with a stream of different type 242   @note Terminates if called with a stream of different type
226   than the original. 243   than the original.
227   */ 244   */
228   template<ReadStream S> 245   template<ReadStream S>
229   void 246   void
230   rebind(S& new_stream) noexcept 247   rebind(S& new_stream) noexcept
231   { 248   {
232   if(vt_ != &vtable_for_impl<S>::value) 249   if(vt_ != &vtable_for_impl<S>::value)
233   std::terminate(); 250   std::terminate();
234   stream_ = &new_stream; 251   stream_ = &new_stream;
235   } 252   }
236   }; 253   };
237   254  
238   struct any_read_stream::vtable 255   struct any_read_stream::vtable
239   { 256   {
240   // ordered by call frequency for cache line coherence 257   // ordered by call frequency for cache line coherence
241   void (*construct_awaitable)( 258   void (*construct_awaitable)(
242   void* stream, 259   void* stream,
243   void* storage, 260   void* storage,
244   std::span<mutable_buffer const> buffers); 261   std::span<mutable_buffer const> buffers);
245   bool (*await_ready)(void*); 262   bool (*await_ready)(void*);
246   std::coroutine_handle<> (*await_suspend)(void*, std::coroutine_handle<>, io_env const*); 263   std::coroutine_handle<> (*await_suspend)(void*, std::coroutine_handle<>, io_env const*);
247   io_result<std::size_t> (*await_resume)(void*); 264   io_result<std::size_t> (*await_resume)(void*);
248   void (*destroy_awaitable)(void*) noexcept; 265   void (*destroy_awaitable)(void*) noexcept;
249   std::size_t awaitable_size; 266   std::size_t awaitable_size;
250   std::size_t awaitable_align; 267   std::size_t awaitable_align;
251   void (*destroy)(void*) noexcept; 268   void (*destroy)(void*) noexcept;
252   }; 269   };
253   270  
254   template<ReadStream S> 271   template<ReadStream S>
255   struct any_read_stream::vtable_for_impl 272   struct any_read_stream::vtable_for_impl
256   { 273   {
257   using Awaitable = decltype(std::declval<S&>().read_some( 274   using Awaitable = decltype(std::declval<S&>().read_some(
258   std::span<mutable_buffer const>{})); 275   std::span<mutable_buffer const>{}));
259   276  
260   static void 277   static void
HITCBC 261   4 do_destroy_impl(void* stream) noexcept 278   4 do_destroy_impl(void* stream) noexcept
262   { 279   {
HITCBC 263   4 static_cast<S*>(stream)->~S(); 280   4 static_cast<S*>(stream)->~S();
HITCBC 264   4 } 281   4 }
265   282  
266   static void 283   static void
HITCBC 267   103 construct_awaitable_impl( 284   103 construct_awaitable_impl(
268   void* stream, 285   void* stream,
269   void* storage, 286   void* storage,
270   std::span<mutable_buffer const> buffers) 287   std::span<mutable_buffer const> buffers)
271   { 288   {
HITCBC 272   103 auto& s = *static_cast<S*>(stream); 289   103 auto& s = *static_cast<S*>(stream);
HITCBC 273   103 ::new(storage) Awaitable(s.read_some(buffers)); 290   103 ::new(storage) Awaitable(s.read_some(buffers));
HITCBC 274   103 } 291   103 }
275   292  
276   static constexpr vtable value = { 293   static constexpr vtable value = {
277   &construct_awaitable_impl, 294   &construct_awaitable_impl,
HITCBC 278   103 +[](void* p) { 295   103 +[](void* p) {
HITCBC 279   103 return static_cast<Awaitable*>(p)->await_ready(); 296   103 return static_cast<Awaitable*>(p)->await_ready();
280   }, 297   },
HITCBC 281   77 +[](void* p, std::coroutine_handle<> h, io_env const* env) { 298   77 +[](void* p, std::coroutine_handle<> h, io_env const* env) {
HITCBC 282   77 return detail::call_await_suspend( 299   77 return detail::call_await_suspend(
HITCBC 283   77 static_cast<Awaitable*>(p), h, env); 300   77 static_cast<Awaitable*>(p), h, env);
284   }, 301   },
HITCBC 285   101 +[](void* p) { 302   101 +[](void* p) {
HITCBC 286   101 return static_cast<Awaitable*>(p)->await_resume(); 303   101 return static_cast<Awaitable*>(p)->await_resume();
287   }, 304   },
HITCBC 288   115 +[](void* p) noexcept { 305   115 +[](void* p) noexcept {
HITCBC 289   26 static_cast<Awaitable*>(p)->~Awaitable(); 306   26 static_cast<Awaitable*>(p)->~Awaitable();
290   }, 307   },
291   sizeof(Awaitable), 308   sizeof(Awaitable),
292   alignof(Awaitable), 309   alignof(Awaitable),
293   &do_destroy_impl 310   &do_destroy_impl
294   }; 311   };
295   }; 312   };
296   313  
297   inline 314   inline
HITCBC 298   123 any_read_stream::~any_read_stream() 315   123 any_read_stream::~any_read_stream()
299   { 316   {
HITCBC 300   123 if(storage_) 317   123 if(storage_)
301   { 318   {
HITCBC 302   3 vt_->destroy(stream_); 319   3 vt_->destroy(stream_);
HITCBC 303   3 ::operator delete(storage_); 320   3 ::operator delete(storage_);
304   } 321   }
HITCBC 305   123 if(cached_awaitable_) 322   123 if(cached_awaitable_)
306   { 323   {
HITCBC 307   106 if(awaitable_active_) 324   106 if(awaitable_active_)
HITCBC 308   1 vt_->destroy_awaitable(cached_awaitable_); 325   1 vt_->destroy_awaitable(cached_awaitable_);
HITCBC 309   106 ::operator delete(cached_awaitable_); 326   106 ::operator delete(cached_awaitable_);
310   } 327   }
HITCBC 311   123 } 328   123 }
312   329  
313   inline any_read_stream& 330   inline any_read_stream&
HITCBC 314   10 any_read_stream::operator=(any_read_stream&& other) noexcept 331   10 any_read_stream::operator=(any_read_stream&& other) noexcept
315   { 332   {
HITCBC 316   10 if(this != &other) 333   10 if(this != &other)
317   { 334   {
HITCBC 318   10 if(storage_) 335   10 if(storage_)
319   { 336   {
HITCBC 320   1 vt_->destroy(stream_); 337   1 vt_->destroy(stream_);
HITCBC 321   1 ::operator delete(storage_); 338   1 ::operator delete(storage_);
322   } 339   }
HITCBC 323   10 if(cached_awaitable_) 340   10 if(cached_awaitable_)
324   { 341   {
HITCBC 325   4 if(awaitable_active_) 342   4 if(awaitable_active_)
HITCBC 326   1 vt_->destroy_awaitable(cached_awaitable_); 343   1 vt_->destroy_awaitable(cached_awaitable_);
HITCBC 327   4 ::operator delete(cached_awaitable_); 344   4 ::operator delete(cached_awaitable_);
328   } 345   }
HITCBC 329   10 stream_ = std::exchange(other.stream_, nullptr); 346   10 stream_ = std::exchange(other.stream_, nullptr);
HITCBC 330   10 vt_ = std::exchange(other.vt_, nullptr); 347   10 vt_ = std::exchange(other.vt_, nullptr);
HITCBC 331   10 cached_awaitable_ = std::exchange(other.cached_awaitable_, nullptr); 348   10 cached_awaitable_ = std::exchange(other.cached_awaitable_, nullptr);
HITCBC 332   10 storage_ = std::exchange(other.storage_, nullptr); 349   10 storage_ = std::exchange(other.storage_, nullptr);
HITCBC 333   10 awaitable_active_ = std::exchange(other.awaitable_active_, false); 350   10 awaitable_active_ = std::exchange(other.awaitable_active_, false);
334   } 351   }
HITCBC 335   10 return *this; 352   10 return *this;
336   } 353   }
337   354  
338   template<ReadStream S> 355   template<ReadStream S>
339   requires (!std::same_as<std::decay_t<S>, any_read_stream>) 356   requires (!std::same_as<std::decay_t<S>, any_read_stream>)
HITCBC 340   5 any_read_stream::any_read_stream(S s) 357   5 any_read_stream::any_read_stream(S s)
HITCBC 341   5 : vt_(&vtable_for_impl<S>::value) 358   5 : vt_(&vtable_for_impl<S>::value)
342   { 359   {
343   struct guard { 360   struct guard {
344   any_read_stream* self; 361   any_read_stream* self;
345   bool committed = false; 362   bool committed = false;
HITCBC 346   5 ~guard() { 363   5 ~guard() {
HITCBC 347   5 if(!committed && self->storage_) { 364   5 if(!committed && self->storage_) {
HITCBC 348   1 if(self->stream_) 365   1 if(self->stream_)
349   self->vt_->destroy(self->stream_); // LCOV_EXCL_LINE OOM rollback: only when the cached-awaitable allocation throws 366   self->vt_->destroy(self->stream_); // LCOV_EXCL_LINE OOM rollback: only when the cached-awaitable allocation throws
HITCBC 350   1 ::operator delete(self->storage_); 367   1 ::operator delete(self->storage_);
HITCBC 351   1 self->storage_ = nullptr; 368   1 self->storage_ = nullptr;
HITCBC 352   1 self->stream_ = nullptr; 369   1 self->stream_ = nullptr;
353   } 370   }
HITCBC 354   5 } 371   5 }
HITCBC 355   5 } g{this}; 372   5 } g{this};
356   373  
HITCBC 357   5 storage_ = ::operator new(sizeof(S)); 374   5 storage_ = ::operator new(sizeof(S));
HITCBC 358   5 stream_ = ::new(storage_) S(std::move(s)); 375   5 stream_ = ::new(storage_) S(std::move(s));
359   376  
360   // Preallocate the awaitable storage 377   // Preallocate the awaitable storage
HITCBC 361   4 cached_awaitable_ = ::operator new(vt_->awaitable_size); 378   4 cached_awaitable_ = ::operator new(vt_->awaitable_size);
362   379  
HITCBC 363   4 g.committed = true; 380   4 g.committed = true;
HITCBC 364   5 } 381   5 }
365   382  
366   template<ReadStream S> 383   template<ReadStream S>
HITCBC 367   106 any_read_stream::any_read_stream(S* s) 384   106 any_read_stream::any_read_stream(S* s)
HITCBC 368   106 : stream_(s) 385   106 : stream_(s)
HITCBC 369   106 , vt_(&vtable_for_impl<S>::value) 386   106 , vt_(&vtable_for_impl<S>::value)
370   { 387   {
371   // Preallocate the awaitable storage 388   // Preallocate the awaitable storage
HITCBC 372   106 cached_awaitable_ = ::operator new(vt_->awaitable_size); 389   106 cached_awaitable_ = ::operator new(vt_->awaitable_size);
HITCBC 373   106 } 390   106 }
374   391  
375   template<MutableBufferSequence MB> 392   template<MutableBufferSequence MB>
376   auto 393   auto
HITCBC 377   103 any_read_stream::read_some(MB buffers) 394   103 any_read_stream::read_some(MB buffers)
378   { 395   {
379   // VFALCO in theory, we could use if constexpr to detect a 396   // VFALCO in theory, we could use if constexpr to detect a
380   // span and then pass that through to read_some without the array 397   // span and then pass that through to read_some without the array
381   // LCOV_EXCL_START read_some awaitable: exercised by tests, but the 398   // LCOV_EXCL_START read_some awaitable: exercised by tests, but the
382   // coverage tooling reports its templated body uncovered per-instantiation 399   // coverage tooling reports its templated body uncovered per-instantiation
383   struct awaitable 400   struct awaitable
384   { 401   {
385   any_read_stream* self_; 402   any_read_stream* self_;
386   detail::mutable_buffer_array<detail::max_iovec_> ba_; 403   detail::mutable_buffer_array<detail::max_iovec_> ba_;
387   404  
388   bool 405   bool
389   await_ready() 406   await_ready()
390   { 407   {
391   self_->vt_->construct_awaitable( 408   self_->vt_->construct_awaitable(
392   self_->stream_, 409   self_->stream_,
393   self_->cached_awaitable_, 410   self_->cached_awaitable_,
394   ba_.to_span()); 411   ba_.to_span());
395   self_->awaitable_active_ = true; 412   self_->awaitable_active_ = true;
396   413  
397   return self_->vt_->await_ready( 414   return self_->vt_->await_ready(
398   self_->cached_awaitable_); 415   self_->cached_awaitable_);
399   } 416   }
400   417  
401   std::coroutine_handle<> 418   std::coroutine_handle<>
402   await_suspend(std::coroutine_handle<> h, io_env const* env) 419   await_suspend(std::coroutine_handle<> h, io_env const* env)
403   { 420   {
404   return self_->vt_->await_suspend( 421   return self_->vt_->await_suspend(
405   self_->cached_awaitable_, h, env); 422   self_->cached_awaitable_, h, env);
406   } 423   }
407   424  
408   io_result<std::size_t> 425   io_result<std::size_t>
409   await_resume() 426   await_resume()
410   { 427   {
411   struct guard { 428   struct guard {
412   any_read_stream* self; 429   any_read_stream* self;
413   ~guard() { 430   ~guard() {
414   self->vt_->destroy_awaitable(self->cached_awaitable_); 431   self->vt_->destroy_awaitable(self->cached_awaitable_);
415   self->awaitable_active_ = false; 432   self->awaitable_active_ = false;
416   } 433   }
417   } g{self_}; 434   } g{self_};
418   return self_->vt_->await_resume( 435   return self_->vt_->await_resume(
419   self_->cached_awaitable_); 436   self_->cached_awaitable_);
420   } 437   }
421   }; 438   };
422   // LCOV_EXCL_STOP 439   // LCOV_EXCL_STOP
423   return awaitable{this, 440   return awaitable{this,
HITCBC 424   103 detail::mutable_buffer_array<detail::max_iovec_>(buffers)}; 441   103 detail::mutable_buffer_array<detail::max_iovec_>(buffers)};
HITCBC 425   103 } 442   103 }
426   443  
427   } // namespace capy 444   } // namespace capy
428   } // namespace boost 445   } // namespace boost
429   446  
430   #endif 447   #endif