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