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