100.00% Lines (210/210) 100.00% Functions (52/52)
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   // 3   //
4   // Distributed under the Boost Software License, Version 1.0. (See accompanying 4   // 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) 5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6   // 6   //
7   // Official repository: https://github.com/cppalliance/capy 7   // Official repository: https://github.com/cppalliance/capy
8   // 8   //
9   9  
10   #ifndef BOOST_CAPY_IO_ANY_WRITE_SINK_HPP 10   #ifndef BOOST_CAPY_IO_ANY_WRITE_SINK_HPP
11   #define BOOST_CAPY_IO_ANY_WRITE_SINK_HPP 11   #define BOOST_CAPY_IO_ANY_WRITE_SINK_HPP
12   12  
13   #include <boost/capy/detail/config.hpp> 13   #include <boost/capy/detail/config.hpp>
14   #include <boost/capy/detail/await_suspend_helper.hpp> 14   #include <boost/capy/detail/await_suspend_helper.hpp>
15   #include <boost/capy/buffers.hpp> 15   #include <boost/capy/buffers.hpp>
16   #include <boost/capy/detail/buffer_array.hpp> 16   #include <boost/capy/detail/buffer_array.hpp>
17   #include <boost/capy/buffers/buffer_param.hpp> 17   #include <boost/capy/buffers/buffer_param.hpp>
18   #include <boost/capy/concept/io_awaitable.hpp> 18   #include <boost/capy/concept/io_awaitable.hpp>
19   #include <boost/capy/concept/write_sink.hpp> 19   #include <boost/capy/concept/write_sink.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   #include <boost/capy/io_task.hpp> 23   #include <boost/capy/io_task.hpp>
24   24  
25   #include <concepts> 25   #include <concepts>
26   #include <coroutine> 26   #include <coroutine>
27   #include <cstddef> 27   #include <cstddef>
28   #include <exception> 28   #include <exception>
29   #include <new> 29   #include <new>
30   #include <span> 30   #include <span>
31   #include <stop_token> 31   #include <stop_token>
32   #include <system_error> 32   #include <system_error>
33   #include <utility> 33   #include <utility>
34   34  
35   namespace boost { 35   namespace boost {
36   namespace capy { 36   namespace capy {
37   37  
38   /** Type-erased wrapper for any WriteSink. 38   /** Type-erased wrapper for any WriteSink.
39   39  
40   This class provides type erasure for any type satisfying the 40   This class provides type erasure for any type satisfying the
41   @ref WriteSink concept, enabling runtime polymorphism for 41   @ref WriteSink concept, enabling runtime polymorphism for
42   sink write operations. It uses cached awaitable storage to achieve 42   sink write operations. It uses cached awaitable storage to achieve
43   zero steady-state allocation after construction. 43   zero steady-state allocation after construction.
44   44  
45   The wrapper supports two construction modes: 45   The wrapper supports two construction modes:
46   - **Owning**: Pass by value to transfer ownership. The wrapper 46   - **Owning**: Pass by value to transfer ownership. The wrapper
47   allocates storage and owns the sink. 47   allocates storage and owns the sink.
48   - **Reference**: Pass a pointer to wrap without ownership. The 48   - **Reference**: Pass a pointer to wrap without ownership. The
49   pointed-to sink must outlive this wrapper. 49   pointed-to sink must outlive this wrapper.
50   50  
51   @par Awaitable Preallocation 51   @par Awaitable Preallocation
52   The constructor preallocates storage for the type-erased awaitable. 52   The constructor preallocates storage for the type-erased awaitable.
53   This reserves all virtual address space at server startup 53   This reserves all virtual address space at server startup
54   so memory usage can be measured up front, rather than 54   so memory usage can be measured up front, rather than
55   allocating piecemeal as traffic arrives. 55   allocating piecemeal as traffic arrives.
56   56  
57   @par Immediate Completion 57   @par Immediate Completion
58   Operations complete immediately without suspending when the 58   Operations complete immediately without suspending when the
59   buffer sequence is empty, or when the underlying sink's 59   buffer sequence is empty, or when the underlying sink's
60   awaitable reports readiness via `await_ready`. 60   awaitable reports readiness via `await_ready`.
61   61  
62   @par Thread Safety 62   @par Thread Safety
63   Not thread-safe. Concurrent operations on the same wrapper 63   Not thread-safe. Concurrent operations on the same wrapper
64   are undefined behavior. 64   are undefined behavior.
65   65  
66   @par Example 66   @par Example
67   @code 67   @code
68   // Owning - takes ownership of the sink 68   // Owning - takes ownership of the sink
69   any_write_sink ws(some_sink{args...}); 69   any_write_sink ws(some_sink{args...});
70   70  
71   // Reference - wraps without ownership 71   // Reference - wraps without ownership
72   some_sink sink; 72   some_sink sink;
73   any_write_sink ws(&sink); 73   any_write_sink ws(&sink);
74   74  
75   const_buffer buf(data, size); 75   const_buffer buf(data, size);
76   auto [ec, n] = co_await ws.write(std::span(&buf, 1)); 76   auto [ec, n] = co_await ws.write(std::span(&buf, 1));
77   auto [ec2] = co_await ws.write_eof(); 77   auto [ec2] = co_await ws.write_eof();
78   @endcode 78   @endcode
79   79  
80   @see any_write_stream, WriteSink 80   @see any_write_stream, WriteSink
81   */ 81   */
82   class any_write_sink 82   class any_write_sink
83   { 83   {
84   struct vtable; 84   struct vtable;
85   struct write_awaitable_ops; 85   struct write_awaitable_ops;
86   struct eof_awaitable_ops; 86   struct eof_awaitable_ops;
87   87  
88   template<WriteSink S> 88   template<WriteSink S>
89   struct vtable_for_impl; 89   struct vtable_for_impl;
90   90  
91   void* sink_ = nullptr; 91   void* sink_ = nullptr;
92   vtable const* vt_ = nullptr; 92   vtable const* vt_ = nullptr;
93   void* cached_awaitable_ = nullptr; 93   void* cached_awaitable_ = nullptr;
94   void* storage_ = nullptr; 94   void* storage_ = nullptr;
95   write_awaitable_ops const* active_write_ops_ = nullptr; 95   write_awaitable_ops const* active_write_ops_ = nullptr;
96   eof_awaitable_ops const* active_eof_ops_ = nullptr; 96   eof_awaitable_ops const* active_eof_ops_ = nullptr;
97   97  
98   public: 98   public:
99   /** Destructor. 99   /** Destructor.
100   100  
101   Destroys the owned sink (if any) and releases the cached 101   Destroys the owned sink (if any) and releases the cached
102   awaitable storage. 102   awaitable storage.
103   */ 103   */
104   ~any_write_sink(); 104   ~any_write_sink();
105   105  
106   /** Construct a default instance. 106   /** Construct a default instance.
107   107  
108   Constructs an empty wrapper. Operations on a default-constructed 108   Constructs an empty wrapper. Operations on a default-constructed
109   wrapper result in undefined behavior. 109   wrapper result in undefined behavior.
110   */ 110   */
111   any_write_sink() = default; 111   any_write_sink() = default;
112   112  
113   /** Non-copyable. 113   /** Non-copyable.
114   114  
115   The awaitable cache is per-instance and cannot be shared. 115   The awaitable cache is per-instance and cannot be shared.
116   */ 116   */
117   any_write_sink(any_write_sink const&) = delete; 117   any_write_sink(any_write_sink const&) = delete;
118   any_write_sink& operator=(any_write_sink const&) = delete; 118   any_write_sink& operator=(any_write_sink const&) = delete;
119   119  
120   /** Construct by moving. 120   /** Construct by moving.
121   121  
122   Transfers ownership of the wrapped sink (if owned) and 122   Transfers ownership of the wrapped sink (if owned) and
123   cached awaitable storage from `other`. After the move, `other` is 123   cached awaitable storage from `other`. After the move, `other` is
124   in a default-constructed state. 124   in a default-constructed state.
125   125  
126   @param other The wrapper to move from. 126   @param other The wrapper to move from.
127   */ 127   */
HITCBC 128   1 any_write_sink(any_write_sink&& other) noexcept 128   1 any_write_sink(any_write_sink&& other) noexcept
HITCBC 129   1 : sink_(std::exchange(other.sink_, nullptr)) 129   1 : sink_(std::exchange(other.sink_, nullptr))
HITCBC 130   1 , vt_(std::exchange(other.vt_, nullptr)) 130   1 , vt_(std::exchange(other.vt_, nullptr))
HITCBC 131   1 , cached_awaitable_(std::exchange(other.cached_awaitable_, nullptr)) 131   1 , cached_awaitable_(std::exchange(other.cached_awaitable_, nullptr))
HITCBC 132   1 , storage_(std::exchange(other.storage_, nullptr)) 132   1 , storage_(std::exchange(other.storage_, nullptr))
HITCBC 133   1 , active_write_ops_(std::exchange(other.active_write_ops_, nullptr)) 133   1 , active_write_ops_(std::exchange(other.active_write_ops_, nullptr))
HITCBC 134   1 , active_eof_ops_(std::exchange(other.active_eof_ops_, nullptr)) 134   1 , active_eof_ops_(std::exchange(other.active_eof_ops_, nullptr))
135   { 135   {
HITCBC 136   1 } 136   1 }
137   137  
138   /** Assign by moving. 138   /** Assign by moving.
139   139  
140   Destroys any owned sink and releases existing resources, 140   Destroys any owned sink and releases existing resources,
141   then transfers ownership from `other`. 141   then transfers ownership from `other`.
142   142  
143   @param other The wrapper to move from. 143   @param other The wrapper to move from.
144   @return Reference to this wrapper. 144   @return Reference to this wrapper.
145   */ 145   */
146   any_write_sink& 146   any_write_sink&
147   operator=(any_write_sink&& other) noexcept; 147   operator=(any_write_sink&& other) noexcept;
148   148  
149   /** Construct by taking ownership of a WriteSink. 149   /** Construct by taking ownership of a WriteSink.
150   150  
151   Allocates storage and moves the sink into this wrapper. 151   Allocates storage and moves the sink into this wrapper.
152   The wrapper owns the sink and will destroy it. 152   The wrapper owns the sink and will destroy it.
153   153  
154   @param s The sink to take ownership of. 154   @param s The sink to take ownership of.
155   */ 155   */
156   template<WriteSink S> 156   template<WriteSink S>
157   requires (!std::same_as<std::decay_t<S>, any_write_sink>) 157   requires (!std::same_as<std::decay_t<S>, any_write_sink>)
158   any_write_sink(S s); 158   any_write_sink(S s);
159   159  
160   /** Construct by wrapping a WriteSink without ownership. 160   /** Construct by wrapping a WriteSink without ownership.
161   161  
162   Wraps the given sink by pointer. The sink must remain 162   Wraps the given sink by pointer. The sink must remain
163   valid for the lifetime of this wrapper. 163   valid for the lifetime of this wrapper.
164   164  
165   @param s Pointer to the sink to wrap. 165   @param s Pointer to the sink to wrap.
166   */ 166   */
167   template<WriteSink S> 167   template<WriteSink S>
168   any_write_sink(S* s); 168   any_write_sink(S* s);
169   169  
170   /** Check if the wrapper contains a valid sink. 170   /** Check if the wrapper contains a valid sink.
171   171  
172   @return `true` if wrapping a sink, `false` if default-constructed 172   @return `true` if wrapping a sink, `false` if default-constructed
173   or moved-from. 173   or moved-from.
174   */ 174   */
175   bool 175   bool
HITCBC 176   18 has_value() const noexcept 176   18 has_value() const noexcept
177   { 177   {
HITCBC 178   18 return sink_ != nullptr; 178   18 return sink_ != nullptr;
179   } 179   }
180   180  
181   /** Check if the wrapper contains a valid sink. 181   /** Check if the wrapper contains a valid sink.
182   182  
183   @return `true` if wrapping a sink, `false` if default-constructed 183   @return `true` if wrapping a sink, `false` if default-constructed
184   or moved-from. 184   or moved-from.
185   */ 185   */
186   explicit 186   explicit
HITCBC 187   2 operator bool() const noexcept 187   2 operator bool() const noexcept
188   { 188   {
HITCBC 189   2 return has_value(); 189   2 return has_value();
190   } 190   }
191   191  
192   /** Initiate a partial write operation. 192   /** Initiate a partial write operation.
193   193  
194   Attempt to write up to `buffer_size( buffers )` bytes from 194   Attempt to write up to `buffer_size( buffers )` bytes from
195   the provided buffer sequence. May consume less than the 195   the provided buffer sequence. May consume less than the
196   full sequence. 196   full sequence.
197   197  
198   @param buffers The buffer sequence containing data to write. 198   @param buffers The buffer sequence containing data to write.
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 sink's awaitable reports immediate 206   @li The underlying sink'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 @ref write for guaranteed 210   entire buffer sequence. Use @ref write for guaranteed
211   complete transfer. 211   complete transfer.
212   212  
213   @par Preconditions 213   @par Preconditions
214   The wrapper must contain a valid sink (`has_value() == true`). 214   The wrapper must contain a valid sink (`has_value() == true`).
215   */ 215   */
216   template<ConstBufferSequence CB> 216   template<ConstBufferSequence CB>
217   auto 217   auto
218   write_some(CB buffers); 218   write_some(CB buffers);
219   219  
220   /** Initiate a complete write operation. 220   /** Initiate a complete write operation.
221   221  
222   Writes data from the provided buffer sequence. The operation 222   Writes data from the provided buffer sequence. The operation
223   completes when all bytes have been consumed, or an error 223   completes when all bytes have been consumed, or an error
224   occurs. Forwards to the underlying sink's `write` operation, 224   occurs. Forwards to the underlying sink's `write` operation,
225   windowed through @ref buffer_param when the sequence exceeds 225   windowed through @ref buffer_param when the sequence exceeds
226   the per-call buffer limit. 226   the per-call buffer limit.
227   227  
228   @param buffers The buffer sequence containing data to write. 228   @param buffers The buffer sequence containing data to write.
229   229  
230   @return An awaitable that await-returns `(error_code,std::size_t)`. 230   @return An awaitable that await-returns `(error_code,std::size_t)`.
231   231  
232   @par Immediate Completion 232   @par Immediate Completion
233   The operation completes immediately without suspending 233   The operation completes immediately without suspending
234   the calling coroutine when: 234   the calling coroutine when:
235   @li The buffer sequence is empty, returning `{error_code{}, 0}`. 235   @li The buffer sequence is empty, returning `{error_code{}, 0}`.
236   @li Every underlying `write` call completes 236   @li Every underlying `write` call completes
237   immediately (the wrapped sink reports readiness 237   immediately (the wrapped sink reports readiness
238   via `await_ready` on each iteration). 238   via `await_ready` on each iteration).
239   239  
240   @par Preconditions 240   @par Preconditions
241   The wrapper must contain a valid sink (`has_value() == true`). 241   The wrapper must contain a valid sink (`has_value() == true`).
242   */ 242   */
243   template<ConstBufferSequence CB> 243   template<ConstBufferSequence CB>
244   io_task<std::size_t> 244   io_task<std::size_t>
245   write(CB buffers); 245   write(CB buffers);
246   246  
247   /** Atomically write data and signal end-of-stream. 247   /** Atomically write data and signal end-of-stream.
248   248  
249   Writes all data from the buffer sequence and then signals 249   Writes all data from the buffer sequence and then signals
250   end-of-stream. The implementation decides how to partition 250   end-of-stream. The implementation decides how to partition
251   the data across calls to the underlying sink's @ref write 251   the data across calls to the underlying sink's @ref write
252   and `write_eof`. When the caller's buffer sequence is 252   and `write_eof`. When the caller's buffer sequence is
253   non-empty, the final call to the underlying sink is always 253   non-empty, the final call to the underlying sink is always
254   `write_eof` with a non-empty buffer sequence. When the 254   `write_eof` with a non-empty buffer sequence. When the
255   caller's buffer sequence is empty, only `write_eof()` with 255   caller's buffer sequence is empty, only `write_eof()` with
256   no data is called. 256   no data is called.
257   257  
258   @param buffers The buffer sequence containing data to write. 258   @param buffers The buffer sequence containing data to write.
259   259  
260   @return An awaitable that await-returns `(error_code,std::size_t)`. 260   @return An awaitable that await-returns `(error_code,std::size_t)`.
261   261  
262   @par Immediate Completion 262   @par Immediate Completion
263   The operation completes immediately without suspending 263   The operation completes immediately without suspending
264   the calling coroutine when: 264   the calling coroutine when:
265   @li The buffer sequence is empty. Only the @ref write_eof() 265   @li The buffer sequence is empty. Only the @ref write_eof()
266   call is performed. 266   call is performed.
267   @li All underlying operations complete immediately (the 267   @li All underlying operations complete immediately (the
268   wrapped sink reports readiness via `await_ready`). 268   wrapped sink reports readiness via `await_ready`).
269   269  
270   @par Preconditions 270   @par Preconditions
271   The wrapper must contain a valid sink (`has_value() == true`). 271   The wrapper must contain a valid sink (`has_value() == true`).
272   */ 272   */
273   template<ConstBufferSequence CB> 273   template<ConstBufferSequence CB>
274   io_task<std::size_t> 274   io_task<std::size_t>
275   write_eof(CB buffers); 275   write_eof(CB buffers);
276   276  
277   /** Signal end of data. 277   /** Signal end of data.
278   278  
279   Indicates that no more data will be written to the sink. 279   Indicates that no more data will be written to the sink.
280   The operation completes when the sink is finalized, or 280   The operation completes when the sink is finalized, or
281   an error occurs. 281   an error occurs.
282   282  
283   @return An awaitable that await-returns `(error_code)`. 283   @return An awaitable that await-returns `(error_code)`.
284   284  
285   @par Immediate Completion 285   @par Immediate Completion
286   The operation completes immediately without suspending 286   The operation completes immediately without suspending
287   the calling coroutine when the underlying sink's awaitable 287   the calling coroutine when the underlying sink's awaitable
288   reports immediate readiness via `await_ready`. 288   reports immediate readiness via `await_ready`.
289   289  
290   @par Preconditions 290   @par Preconditions
291   The wrapper must contain a valid sink (`has_value() == true`). 291   The wrapper must contain a valid sink (`has_value() == true`).
292   */ 292   */
293   auto 293   auto
294   write_eof(); 294   write_eof();
295   295  
296   protected: 296   protected:
297   /** Rebind to a new sink after move. 297   /** Rebind to a new sink after move.
298   298  
299   Updates the internal pointer to reference a new sink object. 299   Updates the internal pointer to reference a new sink object.
300   Used by owning wrappers after move assignment when the owned 300   Used by owning wrappers after move assignment when the owned
301   object has moved to a new location. 301   object has moved to a new location.
302   302  
303   @param new_sink The new sink to bind to. Must be the same 303   @param new_sink The new sink to bind to. Must be the same
304   type as the original sink. 304   type as the original sink.
305   305  
306   @note Terminates if called with a sink of different type 306   @note Terminates if called with a sink of different type
307   than the original. 307   than the original.
308   */ 308   */
309   template<WriteSink S> 309   template<WriteSink S>
310   void 310   void
311   rebind(S& new_sink) noexcept 311   rebind(S& new_sink) noexcept
312   { 312   {
313   if(vt_ != &vtable_for_impl<S>::value) 313   if(vt_ != &vtable_for_impl<S>::value)
314   std::terminate(); 314   std::terminate();
315   sink_ = &new_sink; 315   sink_ = &new_sink;
316   } 316   }
317   317  
318   private: 318   private:
319   auto 319   auto
320   write_some_(std::span<const_buffer const> buffers); 320   write_some_(std::span<const_buffer const> buffers);
321   321  
322   auto 322   auto
323   write_(std::span<const_buffer const> buffers); 323   write_(std::span<const_buffer const> buffers);
324   324  
325   auto 325   auto
326   write_eof_buffers_(std::span<const_buffer const> buffers); 326   write_eof_buffers_(std::span<const_buffer const> buffers);
327   }; 327   };
328   328  
329   struct any_write_sink::write_awaitable_ops 329   struct any_write_sink::write_awaitable_ops
330   { 330   {
331   bool (*await_ready)(void*); 331   bool (*await_ready)(void*);
332   std::coroutine_handle<> (*await_suspend)(void*, std::coroutine_handle<>, io_env const*); 332   std::coroutine_handle<> (*await_suspend)(void*, std::coroutine_handle<>, io_env const*);
333   io_result<std::size_t> (*await_resume)(void*); 333   io_result<std::size_t> (*await_resume)(void*);
334   void (*destroy)(void*) noexcept; 334   void (*destroy)(void*) noexcept;
335   }; 335   };
336   336  
337   struct any_write_sink::eof_awaitable_ops 337   struct any_write_sink::eof_awaitable_ops
338   { 338   {
339   bool (*await_ready)(void*); 339   bool (*await_ready)(void*);
340   std::coroutine_handle<> (*await_suspend)(void*, std::coroutine_handle<>, io_env const*); 340   std::coroutine_handle<> (*await_suspend)(void*, std::coroutine_handle<>, io_env const*);
341   io_result<> (*await_resume)(void*); 341   io_result<> (*await_resume)(void*);
342   void (*destroy)(void*) noexcept; 342   void (*destroy)(void*) noexcept;
343   }; 343   };
344   344  
345   struct any_write_sink::vtable 345   struct any_write_sink::vtable
346   { 346   {
347   write_awaitable_ops const* (*construct_write_some_awaitable)( 347   write_awaitable_ops const* (*construct_write_some_awaitable)(
348   void* sink, 348   void* sink,
349   void* storage, 349   void* storage,
350   std::span<const_buffer const> buffers); 350   std::span<const_buffer const> buffers);
351   write_awaitable_ops const* (*construct_write_awaitable)( 351   write_awaitable_ops const* (*construct_write_awaitable)(
352   void* sink, 352   void* sink,
353   void* storage, 353   void* storage,
354   std::span<const_buffer const> buffers); 354   std::span<const_buffer const> buffers);
355   write_awaitable_ops const* (*construct_write_eof_buffers_awaitable)( 355   write_awaitable_ops const* (*construct_write_eof_buffers_awaitable)(
356   void* sink, 356   void* sink,
357   void* storage, 357   void* storage,
358   std::span<const_buffer const> buffers); 358   std::span<const_buffer const> buffers);
359   eof_awaitable_ops const* (*construct_eof_awaitable)( 359   eof_awaitable_ops const* (*construct_eof_awaitable)(
360   void* sink, 360   void* sink,
361   void* storage); 361   void* storage);
362   std::size_t awaitable_size; 362   std::size_t awaitable_size;
363   std::size_t awaitable_align; 363   std::size_t awaitable_align;
364   void (*destroy)(void*) noexcept; 364   void (*destroy)(void*) noexcept;
365   }; 365   };
366   366  
367   template<WriteSink S> 367   template<WriteSink S>
368   struct any_write_sink::vtable_for_impl 368   struct any_write_sink::vtable_for_impl
369   { 369   {
370   using WriteSomeAwaitable = decltype(std::declval<S&>().write_some( 370   using WriteSomeAwaitable = decltype(std::declval<S&>().write_some(
371   std::span<const_buffer const>{})); 371   std::span<const_buffer const>{}));
372   using WriteAwaitable = decltype(std::declval<S&>().write( 372   using WriteAwaitable = decltype(std::declval<S&>().write(
373   std::span<const_buffer const>{})); 373   std::span<const_buffer const>{}));
374   using WriteEofBuffersAwaitable = decltype(std::declval<S&>().write_eof( 374   using WriteEofBuffersAwaitable = decltype(std::declval<S&>().write_eof(
375   std::span<const_buffer const>{})); 375   std::span<const_buffer const>{}));
376   using EofAwaitable = decltype(std::declval<S&>().write_eof()); 376   using EofAwaitable = decltype(std::declval<S&>().write_eof());
377   377  
378   static void 378   static void
HITCBC 379   8 do_destroy_impl(void* sink) noexcept 379   8 do_destroy_impl(void* sink) noexcept
380   { 380   {
HITCBC 381   8 static_cast<S*>(sink)->~S(); 381   8 static_cast<S*>(sink)->~S();
HITCBC 382   8 } 382   8 }
383   383  
384   static write_awaitable_ops const* 384   static write_awaitable_ops const*
HITCBC 385   41 construct_write_some_awaitable_impl( 385   41 construct_write_some_awaitable_impl(
386   void* sink, 386   void* sink,
387   void* storage, 387   void* storage,
388   std::span<const_buffer const> buffers) 388   std::span<const_buffer const> buffers)
389   { 389   {
HITCBC 390   41 auto& s = *static_cast<S*>(sink); 390   41 auto& s = *static_cast<S*>(sink);
HITCBC 391   41 ::new(storage) WriteSomeAwaitable(s.write_some(buffers)); 391   41 ::new(storage) WriteSomeAwaitable(s.write_some(buffers));
392   392  
393   static constexpr write_awaitable_ops ops = { 393   static constexpr write_awaitable_ops ops = {
HITCBC 394   41 +[](void* p) { 394   41 +[](void* p) {
HITCBC 395   41 return static_cast<WriteSomeAwaitable*>(p)->await_ready(); 395   41 return static_cast<WriteSomeAwaitable*>(p)->await_ready();
396   }, 396   },
HITCBC 397   3 +[](void* p, std::coroutine_handle<> h, io_env const* env) { 397   3 +[](void* p, std::coroutine_handle<> h, io_env const* env) {
HITCBC 398   3 return detail::call_await_suspend( 398   3 return detail::call_await_suspend(
HITCBC 399   3 static_cast<WriteSomeAwaitable*>(p), h, env); 399   3 static_cast<WriteSomeAwaitable*>(p), h, env);
400   }, 400   },
HITCBC 401   39 +[](void* p) { 401   39 +[](void* p) {
HITCBC 402   39 return static_cast<WriteSomeAwaitable*>(p)->await_resume(); 402   39 return static_cast<WriteSomeAwaitable*>(p)->await_resume();
403   }, 403   },
HITCBC 404   43 +[](void* p) noexcept { 404   43 +[](void* p) noexcept {
HITCBC 405   2 static_cast<WriteSomeAwaitable*>(p)->~WriteSomeAwaitable(); 405   2 static_cast<WriteSomeAwaitable*>(p)->~WriteSomeAwaitable();
406   } 406   }
407   }; 407   };
HITCBC 408   41 return &ops; 408   41 return &ops;
409   } 409   }
410   410  
411   static write_awaitable_ops const* 411   static write_awaitable_ops const*
HITCBC 412   79 construct_write_awaitable_impl( 412   79 construct_write_awaitable_impl(
413   void* sink, 413   void* sink,
414   void* storage, 414   void* storage,
415   std::span<const_buffer const> buffers) 415   std::span<const_buffer const> buffers)
416   { 416   {
HITCBC 417   79 auto& s = *static_cast<S*>(sink); 417   79 auto& s = *static_cast<S*>(sink);
HITCBC 418   79 ::new(storage) WriteAwaitable(s.write(buffers)); 418   79 ::new(storage) WriteAwaitable(s.write(buffers));
419   419  
420   static constexpr write_awaitable_ops ops = { 420   static constexpr write_awaitable_ops ops = {
HITCBC 421   79 +[](void* p) { 421   79 +[](void* p) {
HITCBC 422   79 return static_cast<WriteAwaitable*>(p)->await_ready(); 422   79 return static_cast<WriteAwaitable*>(p)->await_ready();
423   }, 423   },
HITCBC 424   1 +[](void* p, std::coroutine_handle<> h, io_env const* env) { 424   1 +[](void* p, std::coroutine_handle<> h, io_env const* env) {
HITCBC 425   1 return detail::call_await_suspend( 425   1 return detail::call_await_suspend(
HITCBC 426   1 static_cast<WriteAwaitable*>(p), h, env); 426   1 static_cast<WriteAwaitable*>(p), h, env);
427   }, 427   },
HITCBC 428   79 +[](void* p) { 428   79 +[](void* p) {
HITCBC 429   79 return static_cast<WriteAwaitable*>(p)->await_resume(); 429   79 return static_cast<WriteAwaitable*>(p)->await_resume();
430   }, 430   },
HITCBC 431   79 +[](void* p) noexcept { 431   79 +[](void* p) noexcept {
432   static_cast<WriteAwaitable*>(p)->~WriteAwaitable(); // LCOV_EXCL_LINE runs via destroy tests; gcov miscounts fn-ptr thunk 432   static_cast<WriteAwaitable*>(p)->~WriteAwaitable(); // LCOV_EXCL_LINE runs via destroy tests; gcov miscounts fn-ptr thunk
433   } 433   }
434   }; 434   };
HITCBC 435   79 return &ops; 435   79 return &ops;
436   } 436   }
437   437  
438   static write_awaitable_ops const* 438   static write_awaitable_ops const*
HITCBC 439   17 construct_write_eof_buffers_awaitable_impl( 439   17 construct_write_eof_buffers_awaitable_impl(
440   void* sink, 440   void* sink,
441   void* storage, 441   void* storage,
442   std::span<const_buffer const> buffers) 442   std::span<const_buffer const> buffers)
443   { 443   {
HITCBC 444   17 auto& s = *static_cast<S*>(sink); 444   17 auto& s = *static_cast<S*>(sink);
HITCBC 445   17 ::new(storage) WriteEofBuffersAwaitable(s.write_eof(buffers)); 445   17 ::new(storage) WriteEofBuffersAwaitable(s.write_eof(buffers));
446   446  
447   static constexpr write_awaitable_ops ops = { 447   static constexpr write_awaitable_ops ops = {
HITCBC 448   17 +[](void* p) { 448   17 +[](void* p) {
HITCBC 449   17 return static_cast<WriteEofBuffersAwaitable*>(p)->await_ready(); 449   17 return static_cast<WriteEofBuffersAwaitable*>(p)->await_ready();
450   }, 450   },
HITCBC 451   1 +[](void* p, std::coroutine_handle<> h, io_env const* env) { 451   1 +[](void* p, std::coroutine_handle<> h, io_env const* env) {
HITCBC 452   1 return detail::call_await_suspend( 452   1 return detail::call_await_suspend(
HITCBC 453   1 static_cast<WriteEofBuffersAwaitable*>(p), h, env); 453   1 static_cast<WriteEofBuffersAwaitable*>(p), h, env);
454   }, 454   },
HITCBC 455   17 +[](void* p) { 455   17 +[](void* p) {
HITCBC 456   17 return static_cast<WriteEofBuffersAwaitable*>(p)->await_resume(); 456   17 return static_cast<WriteEofBuffersAwaitable*>(p)->await_resume();
457   }, 457   },
HITCBC 458   17 +[](void* p) noexcept { 458   17 +[](void* p) noexcept {
459   static_cast<WriteEofBuffersAwaitable*>(p)->~WriteEofBuffersAwaitable(); // LCOV_EXCL_LINE runs via destroy tests; gcov miscounts fn-ptr thunk 459   static_cast<WriteEofBuffersAwaitable*>(p)->~WriteEofBuffersAwaitable(); // LCOV_EXCL_LINE runs via destroy tests; gcov miscounts fn-ptr thunk
460   } 460   }
461   }; 461   };
HITCBC 462   17 return &ops; 462   17 return &ops;
463   } 463   }
464   464  
465   static eof_awaitable_ops const* 465   static eof_awaitable_ops const*
HITCBC 466   19 construct_eof_awaitable_impl( 466   19 construct_eof_awaitable_impl(
467   void* sink, 467   void* sink,
468   void* storage) 468   void* storage)
469   { 469   {
HITCBC 470   19 auto& s = *static_cast<S*>(sink); 470   19 auto& s = *static_cast<S*>(sink);
HITCBC 471   19 ::new(storage) EofAwaitable(s.write_eof()); 471   19 ::new(storage) EofAwaitable(s.write_eof());
472   472  
473   static constexpr eof_awaitable_ops ops = { 473   static constexpr eof_awaitable_ops ops = {
HITCBC 474   19 +[](void* p) { 474   19 +[](void* p) {
HITCBC 475   19 return static_cast<EofAwaitable*>(p)->await_ready(); 475   19 return static_cast<EofAwaitable*>(p)->await_ready();
476   }, 476   },
HITCBC 477   3 +[](void* p, std::coroutine_handle<> h, io_env const* env) { 477   3 +[](void* p, std::coroutine_handle<> h, io_env const* env) {
HITCBC 478   3 return detail::call_await_suspend( 478   3 return detail::call_await_suspend(
HITCBC 479   3 static_cast<EofAwaitable*>(p), h, env); 479   3 static_cast<EofAwaitable*>(p), h, env);
480   }, 480   },
HITCBC 481   17 +[](void* p) { 481   17 +[](void* p) {
HITCBC 482   17 return static_cast<EofAwaitable*>(p)->await_resume(); 482   17 return static_cast<EofAwaitable*>(p)->await_resume();
483   }, 483   },
HITCBC 484   21 +[](void* p) noexcept { 484   21 +[](void* p) noexcept {
HITCBC 485   2 static_cast<EofAwaitable*>(p)->~EofAwaitable(); 485   2 static_cast<EofAwaitable*>(p)->~EofAwaitable();
486   } 486   }
487   }; 487   };
HITCBC 488   19 return &ops; 488   19 return &ops;
489   } 489   }
490   490  
491   static constexpr std::size_t max4( 491   static constexpr std::size_t max4(
492   std::size_t a, std::size_t b, 492   std::size_t a, std::size_t b,
493   std::size_t c, std::size_t d) noexcept 493   std::size_t c, std::size_t d) noexcept
494   { 494   {
495   std::size_t ab = a > b ? a : b; 495   std::size_t ab = a > b ? a : b;
496   std::size_t cd = c > d ? c : d; 496   std::size_t cd = c > d ? c : d;
497   return ab > cd ? ab : cd; 497   return ab > cd ? ab : cd;
498   } 498   }
499   499  
500   static constexpr std::size_t max_awaitable_size = 500   static constexpr std::size_t max_awaitable_size =
501   max4(sizeof(WriteSomeAwaitable), 501   max4(sizeof(WriteSomeAwaitable),
502   sizeof(WriteAwaitable), 502   sizeof(WriteAwaitable),
503   sizeof(WriteEofBuffersAwaitable), 503   sizeof(WriteEofBuffersAwaitable),
504   sizeof(EofAwaitable)); 504   sizeof(EofAwaitable));
505   505  
506   static constexpr std::size_t max_awaitable_align = 506   static constexpr std::size_t max_awaitable_align =
507   max4(alignof(WriteSomeAwaitable), 507   max4(alignof(WriteSomeAwaitable),
508   alignof(WriteAwaitable), 508   alignof(WriteAwaitable),
509   alignof(WriteEofBuffersAwaitable), 509   alignof(WriteEofBuffersAwaitable),
510   alignof(EofAwaitable)); 510   alignof(EofAwaitable));
511   511  
512   static constexpr vtable value = { 512   static constexpr vtable value = {
513   &construct_write_some_awaitable_impl, 513   &construct_write_some_awaitable_impl,
514   &construct_write_awaitable_impl, 514   &construct_write_awaitable_impl,
515   &construct_write_eof_buffers_awaitable_impl, 515   &construct_write_eof_buffers_awaitable_impl,
516   &construct_eof_awaitable_impl, 516   &construct_eof_awaitable_impl,
517   max_awaitable_size, 517   max_awaitable_size,
518   max_awaitable_align, 518   max_awaitable_align,
519   &do_destroy_impl 519   &do_destroy_impl
520   }; 520   };
521   }; 521   };
522   522  
523   inline 523   inline
HITCBC 524   134 any_write_sink::~any_write_sink() 524   134 any_write_sink::~any_write_sink()
525   { 525   {
HITCBC 526   134 if(storage_) 526   134 if(storage_)
527   { 527   {
HITCBC 528   7 vt_->destroy(sink_); 528   7 vt_->destroy(sink_);
HITCBC 529   7 ::operator delete(storage_); 529   7 ::operator delete(storage_);
530   } 530   }
HITCBC 531   134 if(cached_awaitable_) 531   134 if(cached_awaitable_)
532   { 532   {
HITCBC 533   126 if(active_write_ops_) 533   126 if(active_write_ops_)
HITCBC 534   1 active_write_ops_->destroy(cached_awaitable_); 534   1 active_write_ops_->destroy(cached_awaitable_);
HITCBC 535   125 else if(active_eof_ops_) 535   125 else if(active_eof_ops_)
HITCBC 536   1 active_eof_ops_->destroy(cached_awaitable_); 536   1 active_eof_ops_->destroy(cached_awaitable_);
HITCBC 537   126 ::operator delete(cached_awaitable_); 537   126 ::operator delete(cached_awaitable_);
538   } 538   }
HITCBC 539   134 } 539   134 }
540   540  
541   inline any_write_sink& 541   inline any_write_sink&
HITCBC 542   4 any_write_sink::operator=(any_write_sink&& other) noexcept 542   4 any_write_sink::operator=(any_write_sink&& other) noexcept
543   { 543   {
HITCBC 544   4 if(this != &other) 544   4 if(this != &other)
545   { 545   {
HITCBC 546   4 if(storage_) 546   4 if(storage_)
547   { 547   {
HITCBC 548   1 vt_->destroy(sink_); 548   1 vt_->destroy(sink_);
HITCBC 549   1 ::operator delete(storage_); 549   1 ::operator delete(storage_);
550   } 550   }
HITCBC 551   4 if(cached_awaitable_) 551   4 if(cached_awaitable_)
552   { 552   {
HITCBC 553   3 if(active_write_ops_) 553   3 if(active_write_ops_)
HITCBC 554   1 active_write_ops_->destroy(cached_awaitable_); 554   1 active_write_ops_->destroy(cached_awaitable_);
HITCBC 555   2 else if(active_eof_ops_) 555   2 else if(active_eof_ops_)
HITCBC 556   1 active_eof_ops_->destroy(cached_awaitable_); 556   1 active_eof_ops_->destroy(cached_awaitable_);
HITCBC 557   3 ::operator delete(cached_awaitable_); 557   3 ::operator delete(cached_awaitable_);
558   } 558   }
HITCBC 559   4 sink_ = std::exchange(other.sink_, nullptr); 559   4 sink_ = std::exchange(other.sink_, nullptr);
HITCBC 560   4 vt_ = std::exchange(other.vt_, nullptr); 560   4 vt_ = std::exchange(other.vt_, nullptr);
HITCBC 561   4 cached_awaitable_ = std::exchange(other.cached_awaitable_, nullptr); 561   4 cached_awaitable_ = std::exchange(other.cached_awaitable_, nullptr);
HITCBC 562   4 storage_ = std::exchange(other.storage_, nullptr); 562   4 storage_ = std::exchange(other.storage_, nullptr);
HITCBC 563   4 active_write_ops_ = std::exchange(other.active_write_ops_, nullptr); 563   4 active_write_ops_ = std::exchange(other.active_write_ops_, nullptr);
HITCBC 564   4 active_eof_ops_ = std::exchange(other.active_eof_ops_, nullptr); 564   4 active_eof_ops_ = std::exchange(other.active_eof_ops_, nullptr);
565   } 565   }
HITCBC 566   4 return *this; 566   4 return *this;
567   } 567   }
568   568  
569   template<WriteSink S> 569   template<WriteSink S>
570   requires (!std::same_as<std::decay_t<S>, any_write_sink>) 570   requires (!std::same_as<std::decay_t<S>, any_write_sink>)
HITCBC 571   9 any_write_sink::any_write_sink(S s) 571   9 any_write_sink::any_write_sink(S s)
HITCBC 572   9 : vt_(&vtable_for_impl<S>::value) 572   9 : vt_(&vtable_for_impl<S>::value)
573   { 573   {
574   struct guard { 574   struct guard {
575   any_write_sink* self; 575   any_write_sink* self;
576   bool committed = false; 576   bool committed = false;
HITCBC 577   9 ~guard() { 577   9 ~guard() {
HITCBC 578   9 if(!committed && self->storage_) { 578   9 if(!committed && self->storage_) {
HITCBC 579   1 if(self->sink_) 579   1 if(self->sink_)
580   self->vt_->destroy(self->sink_); // LCOV_EXCL_LINE OOM rollback: only when the cached-awaitable allocation throws 580   self->vt_->destroy(self->sink_); // LCOV_EXCL_LINE OOM rollback: only when the cached-awaitable allocation throws
HITCBC 581   1 ::operator delete(self->storage_); 581   1 ::operator delete(self->storage_);
HITCBC 582   1 self->storage_ = nullptr; 582   1 self->storage_ = nullptr;
HITCBC 583   1 self->sink_ = nullptr; 583   1 self->sink_ = nullptr;
584   } 584   }
HITCBC 585   9 } 585   9 }
HITCBC 586   9 } g{this}; 586   9 } g{this};
587   587  
HITCBC 588   9 storage_ = ::operator new(sizeof(S)); 588   9 storage_ = ::operator new(sizeof(S));
HITCBC 589   9 sink_ = ::new(storage_) S(std::move(s)); 589   9 sink_ = ::new(storage_) S(std::move(s));
590   590  
591   // Preallocate the awaitable storage (sized for max of write/eof) 591   // Preallocate the awaitable storage (sized for max of write/eof)
HITCBC 592   8 cached_awaitable_ = ::operator new(vt_->awaitable_size); 592   8 cached_awaitable_ = ::operator new(vt_->awaitable_size);
593   593  
HITCBC 594   8 g.committed = true; 594   8 g.committed = true;
HITCBC 595   9 } 595   9 }
596   596  
597   template<WriteSink S> 597   template<WriteSink S>
HITCBC 598   121 any_write_sink::any_write_sink(S* s) 598   121 any_write_sink::any_write_sink(S* s)
HITCBC 599   121 : sink_(s) 599   121 : sink_(s)
HITCBC 600   121 , vt_(&vtable_for_impl<S>::value) 600   121 , vt_(&vtable_for_impl<S>::value)
601   { 601   {
602   // Preallocate the awaitable storage (sized for max of write/eof) 602   // Preallocate the awaitable storage (sized for max of write/eof)
HITCBC 603   121 cached_awaitable_ = ::operator new(vt_->awaitable_size); 603   121 cached_awaitable_ = ::operator new(vt_->awaitable_size);
HITCBC 604   121 } 604   121 }
605   605  
606   inline auto 606   inline auto
607   any_write_sink::write_some_( 607   any_write_sink::write_some_(
608   std::span<const_buffer const> buffers) 608   std::span<const_buffer const> buffers)
609   { 609   {
610   struct awaitable 610   struct awaitable
611   { 611   {
612   any_write_sink* self_; 612   any_write_sink* self_;
613   std::span<const_buffer const> buffers_; 613   std::span<const_buffer const> buffers_;
614   614  
615   bool 615   bool
616   await_ready() const noexcept 616   await_ready() const noexcept
617   { 617   {
618   return false; 618   return false;
619   } 619   }
620   620  
621   std::coroutine_handle<> 621   std::coroutine_handle<>
622   await_suspend(std::coroutine_handle<> h, io_env const* env) 622   await_suspend(std::coroutine_handle<> h, io_env const* env)
623   { 623   {
624   self_->active_write_ops_ = self_->vt_->construct_write_some_awaitable( 624   self_->active_write_ops_ = self_->vt_->construct_write_some_awaitable(
625   self_->sink_, 625   self_->sink_,
626   self_->cached_awaitable_, 626   self_->cached_awaitable_,
627   buffers_); 627   buffers_);
628   628  
629   if(self_->active_write_ops_->await_ready(self_->cached_awaitable_)) 629   if(self_->active_write_ops_->await_ready(self_->cached_awaitable_))
630   return h; 630   return h;
631   631  
632   return self_->active_write_ops_->await_suspend( 632   return self_->active_write_ops_->await_suspend(
633   self_->cached_awaitable_, h, env); 633   self_->cached_awaitable_, h, env);
634   } 634   }
635   635  
636   io_result<std::size_t> 636   io_result<std::size_t>
637   await_resume() 637   await_resume()
638   { 638   {
639   struct guard { 639   struct guard {
640   any_write_sink* self; 640   any_write_sink* self;
641   ~guard() { 641   ~guard() {
642   self->active_write_ops_->destroy(self->cached_awaitable_); 642   self->active_write_ops_->destroy(self->cached_awaitable_);
643   self->active_write_ops_ = nullptr; 643   self->active_write_ops_ = nullptr;
644   } 644   }
645   } g{self_}; 645   } g{self_};
646   return self_->active_write_ops_->await_resume( 646   return self_->active_write_ops_->await_resume(
647   self_->cached_awaitable_); 647   self_->cached_awaitable_);
648   } 648   }
649   }; 649   };
650   return awaitable{this, buffers}; 650   return awaitable{this, buffers};
651   } 651   }
652   652  
653   inline auto 653   inline auto
HITCBC 654   79 any_write_sink::write_( 654   79 any_write_sink::write_(
655   std::span<const_buffer const> buffers) 655   std::span<const_buffer const> buffers)
656   { 656   {
657   struct awaitable 657   struct awaitable
658   { 658   {
659   any_write_sink* self_; 659   any_write_sink* self_;
660   std::span<const_buffer const> buffers_; 660   std::span<const_buffer const> buffers_;
661   661  
662   bool 662   bool
HITCBC 663   79 await_ready() const noexcept 663   79 await_ready() const noexcept
664   { 664   {
HITCBC 665   79 return false; 665   79 return false;
666   } 666   }
667   667  
668   std::coroutine_handle<> 668   std::coroutine_handle<>
HITCBC 669   79 await_suspend(std::coroutine_handle<> h, io_env const* env) 669   79 await_suspend(std::coroutine_handle<> h, io_env const* env)
670   { 670   {
HITCBC 671   158 self_->active_write_ops_ = self_->vt_->construct_write_awaitable( 671   158 self_->active_write_ops_ = self_->vt_->construct_write_awaitable(
HITCBC 672   79 self_->sink_, 672   79 self_->sink_,
HITCBC 673   79 self_->cached_awaitable_, 673   79 self_->cached_awaitable_,
674   buffers_); 674   buffers_);
675   675  
HITCBC 676   79 if(self_->active_write_ops_->await_ready(self_->cached_awaitable_)) 676   79 if(self_->active_write_ops_->await_ready(self_->cached_awaitable_))
HITCBC 677   78 return h; 677   78 return h;
678   678  
HITCBC 679   1 return self_->active_write_ops_->await_suspend( 679   1 return self_->active_write_ops_->await_suspend(
HITCBC 680   1 self_->cached_awaitable_, h, env); 680   1 self_->cached_awaitable_, h, env);
681   } 681   }
682   682  
683   io_result<std::size_t> 683   io_result<std::size_t>
HITCBC 684   79 await_resume() 684   79 await_resume()
685   { 685   {
686   struct guard { 686   struct guard {
687   any_write_sink* self; 687   any_write_sink* self;
HITCBC 688   79 ~guard() { 688   79 ~guard() {
HITCBC 689   79 self->active_write_ops_->destroy(self->cached_awaitable_); 689   79 self->active_write_ops_->destroy(self->cached_awaitable_);
HITCBC 690   79 self->active_write_ops_ = nullptr; 690   79 self->active_write_ops_ = nullptr;
HITCBC 691   79 } 691   79 }
HITCBC 692   79 } g{self_}; 692   79 } g{self_};
HITCBC 693   79 return self_->active_write_ops_->await_resume( 693   79 return self_->active_write_ops_->await_resume(
HITCBC 694   137 self_->cached_awaitable_); 694   137 self_->cached_awaitable_);
HITCBC 695   79 } 695   79 }
696   }; 696   };
HITCBC 697   79 return awaitable{this, buffers}; 697   79 return awaitable{this, buffers};
698   } 698   }
699   699  
700   inline auto 700   inline auto
HITCBC 701   19 any_write_sink::write_eof() 701   19 any_write_sink::write_eof()
702   { 702   {
703   struct awaitable 703   struct awaitable
704   { 704   {
705   any_write_sink* self_; 705   any_write_sink* self_;
706   706  
707   bool 707   bool
HITCBC 708   19 await_ready() const noexcept 708   19 await_ready() const noexcept
709   { 709   {
HITCBC 710   19 return false; 710   19 return false;
711   } 711   }
712   712  
713   std::coroutine_handle<> 713   std::coroutine_handle<>
HITCBC 714   19 await_suspend(std::coroutine_handle<> h, io_env const* env) 714   19 await_suspend(std::coroutine_handle<> h, io_env const* env)
715   { 715   {
716   // Construct the underlying awaitable into cached storage 716   // Construct the underlying awaitable into cached storage
HITCBC 717   38 self_->active_eof_ops_ = self_->vt_->construct_eof_awaitable( 717   38 self_->active_eof_ops_ = self_->vt_->construct_eof_awaitable(
HITCBC 718   19 self_->sink_, 718   19 self_->sink_,
HITCBC 719   19 self_->cached_awaitable_); 719   19 self_->cached_awaitable_);
720   720  
721   // Check if underlying is immediately ready 721   // Check if underlying is immediately ready
HITCBC 722   19 if(self_->active_eof_ops_->await_ready(self_->cached_awaitable_)) 722   19 if(self_->active_eof_ops_->await_ready(self_->cached_awaitable_))
HITCBC 723   16 return h; 723   16 return h;
724   724  
725   // Forward to underlying awaitable 725   // Forward to underlying awaitable
HITCBC 726   3 return self_->active_eof_ops_->await_suspend( 726   3 return self_->active_eof_ops_->await_suspend(
HITCBC 727   3 self_->cached_awaitable_, h, env); 727   3 self_->cached_awaitable_, h, env);
728   } 728   }
729   729  
730   io_result<> 730   io_result<>
HITCBC 731   17 await_resume() 731   17 await_resume()
732   { 732   {
733   struct guard { 733   struct guard {
734   any_write_sink* self; 734   any_write_sink* self;
HITCBC 735   17 ~guard() { 735   17 ~guard() {
HITCBC 736   17 self->active_eof_ops_->destroy(self->cached_awaitable_); 736   17 self->active_eof_ops_->destroy(self->cached_awaitable_);
HITCBC 737   17 self->active_eof_ops_ = nullptr; 737   17 self->active_eof_ops_ = nullptr;
HITCBC 738   17 } 738   17 }
HITCBC 739   17 } g{self_}; 739   17 } g{self_};
HITCBC 740   17 return self_->active_eof_ops_->await_resume( 740   17 return self_->active_eof_ops_->await_resume(
HITCBC 741   29 self_->cached_awaitable_); 741   29 self_->cached_awaitable_);
HITCBC 742   17 } 742   17 }
743   }; 743   };
HITCBC 744   19 return awaitable{this}; 744   19 return awaitable{this};
745   } 745   }
746   746  
747   inline auto 747   inline auto
HITCBC 748   17 any_write_sink::write_eof_buffers_( 748   17 any_write_sink::write_eof_buffers_(
749   std::span<const_buffer const> buffers) 749   std::span<const_buffer const> buffers)
750   { 750   {
751   struct awaitable 751   struct awaitable
752   { 752   {
753   any_write_sink* self_; 753   any_write_sink* self_;
754   std::span<const_buffer const> buffers_; 754   std::span<const_buffer const> buffers_;
755   755  
756   bool 756   bool
HITCBC 757   17 await_ready() const noexcept 757   17 await_ready() const noexcept
758   { 758   {
HITCBC 759   17 return false; 759   17 return false;
760   } 760   }
761   761  
762   std::coroutine_handle<> 762   std::coroutine_handle<>
HITCBC 763   17 await_suspend(std::coroutine_handle<> h, io_env const* env) 763   17 await_suspend(std::coroutine_handle<> h, io_env const* env)
764   { 764   {
HITCBC 765   34 self_->active_write_ops_ = 765   34 self_->active_write_ops_ =
HITCBC 766   34 self_->vt_->construct_write_eof_buffers_awaitable( 766   34 self_->vt_->construct_write_eof_buffers_awaitable(
HITCBC 767   17 self_->sink_, 767   17 self_->sink_,
HITCBC 768   17 self_->cached_awaitable_, 768   17 self_->cached_awaitable_,
769   buffers_); 769   buffers_);
770   770  
HITCBC 771   17 if(self_->active_write_ops_->await_ready(self_->cached_awaitable_)) 771   17 if(self_->active_write_ops_->await_ready(self_->cached_awaitable_))
HITCBC 772   16 return h; 772   16 return h;
773   773  
HITCBC 774   1 return self_->active_write_ops_->await_suspend( 774   1 return self_->active_write_ops_->await_suspend(
HITCBC 775   1 self_->cached_awaitable_, h, env); 775   1 self_->cached_awaitable_, h, env);
776   } 776   }
777   777  
778   io_result<std::size_t> 778   io_result<std::size_t>
HITCBC 779   17 await_resume() 779   17 await_resume()
780   { 780   {
781   struct guard { 781   struct guard {
782   any_write_sink* self; 782   any_write_sink* self;
HITCBC 783   17 ~guard() { 783   17 ~guard() {
HITCBC 784   17 self->active_write_ops_->destroy(self->cached_awaitable_); 784   17 self->active_write_ops_->destroy(self->cached_awaitable_);
HITCBC 785   17 self->active_write_ops_ = nullptr; 785   17 self->active_write_ops_ = nullptr;
HITCBC 786   17 } 786   17 }
HITCBC 787   17 } g{self_}; 787   17 } g{self_};
HITCBC 788   17 return self_->active_write_ops_->await_resume( 788   17 return self_->active_write_ops_->await_resume(
HITCBC 789   29 self_->cached_awaitable_); 789   29 self_->cached_awaitable_);
HITCBC 790   17 } 790   17 }
791   }; 791   };
HITCBC 792   17 return awaitable{this, buffers}; 792   17 return awaitable{this, buffers};
793   } 793   }
794   794  
795   template<ConstBufferSequence CB> 795   template<ConstBufferSequence CB>
796   auto 796   auto
HITCBC 797   43 any_write_sink::write_some(CB buffers) 797   43 any_write_sink::write_some(CB buffers)
798   { 798   {
799   struct awaitable 799   struct awaitable
800   { 800   {
801   any_write_sink* self_; 801   any_write_sink* self_;
802   detail::const_buffer_array<detail::max_iovec_> ba_; 802   detail::const_buffer_array<detail::max_iovec_> ba_;
803   803  
HITCBC 804   43 awaitable( 804   43 awaitable(
805   any_write_sink* self, 805   any_write_sink* self,
806   CB const& buffers) 806   CB const& buffers)
HITCBC 807   43 : self_(self) 807   43 : self_(self)
HITCBC 808   43 , ba_(buffers) 808   43 , ba_(buffers)
809   { 809   {
HITCBC 810   43 } 810   43 }
811   811  
812   bool 812   bool
HITCBC 813   43 await_ready() const noexcept 813   43 await_ready() const noexcept
814   { 814   {
HITCBC 815   43 return ba_.to_span().empty(); 815   43 return ba_.to_span().empty();
816   } 816   }
817   817  
818   std::coroutine_handle<> 818   std::coroutine_handle<>
HITCBC 819   41 await_suspend(std::coroutine_handle<> h, io_env const* env) 819   41 await_suspend(std::coroutine_handle<> h, io_env const* env)
820   { 820   {
HITCBC 821   41 self_->active_write_ops_ = self_->vt_->construct_write_some_awaitable( 821   41 self_->active_write_ops_ = self_->vt_->construct_write_some_awaitable(
HITCBC 822   41 self_->sink_, 822   41 self_->sink_,
HITCBC 823   41 self_->cached_awaitable_, 823   41 self_->cached_awaitable_,
HITCBC 824   41 ba_.to_span()); 824   41 ba_.to_span());
825   825  
HITCBC 826   41 if(self_->active_write_ops_->await_ready(self_->cached_awaitable_)) 826   41 if(self_->active_write_ops_->await_ready(self_->cached_awaitable_))
HITCBC 827   38 return h; 827   38 return h;
828   828  
HITCBC 829   3 return self_->active_write_ops_->await_suspend( 829   3 return self_->active_write_ops_->await_suspend(
HITCBC 830   3 self_->cached_awaitable_, h, env); 830   3 self_->cached_awaitable_, h, env);
831   } 831   }
832   832  
833   io_result<std::size_t> 833   io_result<std::size_t>
HITCBC 834   41 await_resume() 834   41 await_resume()
835   { 835   {
HITCBC 836   41 if(ba_.to_span().empty()) 836   41 if(ba_.to_span().empty())
HITCBC 837   2 return {{}, 0}; 837   2 return {{}, 0};
838   838  
839   struct guard { 839   struct guard {
840   any_write_sink* self; 840   any_write_sink* self;
HITCBC 841   39 ~guard() { 841   39 ~guard() {
HITCBC 842   39 self->active_write_ops_->destroy(self->cached_awaitable_); 842   39 self->active_write_ops_->destroy(self->cached_awaitable_);
HITCBC 843   39 self->active_write_ops_ = nullptr; 843   39 self->active_write_ops_ = nullptr;
HITCBC 844   39 } 844   39 }
HITCBC 845   39 } g{self_}; 845   39 } g{self_};
HITCBC 846   39 return self_->active_write_ops_->await_resume( 846   39 return self_->active_write_ops_->await_resume(
HITCBC 847   39 self_->cached_awaitable_); 847   39 self_->cached_awaitable_);
HITCBC 848   39 } 848   39 }
849   }; 849   };
HITCBC 850   43 return awaitable{this, buffers}; 850   43 return awaitable{this, buffers};
851   } 851   }
852   852  
853   template<ConstBufferSequence CB> 853   template<ConstBufferSequence CB>
854   io_task<std::size_t> 854   io_task<std::size_t>
HITCBC 855   69 any_write_sink::write(CB buffers) 855   69 any_write_sink::write(CB buffers)
856   { 856   {
857   buffer_param<CB> bp(buffers); 857   buffer_param<CB> bp(buffers);
858   std::size_t total = 0; 858   std::size_t total = 0;
859   859  
860   for(;;) 860   for(;;)
861   { 861   {
862   auto bufs = bp.data(); 862   auto bufs = bp.data();
863   if(bufs.empty()) 863   if(bufs.empty())
864   break; 864   break;
865   865  
866   auto [ec, n] = co_await write_(bufs); 866   auto [ec, n] = co_await write_(bufs);
867   total += n; 867   total += n;
868   if(ec) 868   if(ec)
869   co_return {ec, total}; 869   co_return {ec, total};
870   bp.consume(n); 870   bp.consume(n);
871   } 871   }
872   872  
873   co_return {{}, total}; 873   co_return {{}, total};
HITCBC 874   138 } 874   138 }
875   875  
876   template<ConstBufferSequence CB> 876   template<ConstBufferSequence CB>
877   io_task<std::size_t> 877   io_task<std::size_t>
HITCBC 878   27 any_write_sink::write_eof(CB buffers) 878   27 any_write_sink::write_eof(CB buffers)
879   { 879   {
880   const_buffer_param<CB> bp(buffers); 880   const_buffer_param<CB> bp(buffers);
881   std::size_t total = 0; 881   std::size_t total = 0;
882   882  
883   for(;;) 883   for(;;)
884   { 884   {
885   auto bufs = bp.data(); 885   auto bufs = bp.data();
886   if(bufs.empty()) 886   if(bufs.empty())
887   { 887   {
888   auto [ec] = co_await write_eof(); 888   auto [ec] = co_await write_eof();
889   co_return {ec, total}; 889   co_return {ec, total};
890   } 890   }
891   891  
892   if(! bp.more()) 892   if(! bp.more())
893   { 893   {
894   // Last window — send atomically with EOF 894   // Last window — send atomically with EOF
895   auto [ec, n] = co_await write_eof_buffers_(bufs); 895   auto [ec, n] = co_await write_eof_buffers_(bufs);
896   total += n; 896   total += n;
897   co_return {ec, total}; 897   co_return {ec, total};
898   } 898   }
899   899  
900   auto [ec, n] = co_await write_(bufs); 900   auto [ec, n] = co_await write_(bufs);
901   total += n; 901   total += n;
902   if(ec) 902   if(ec)
903   co_return {ec, total}; 903   co_return {ec, total};
904   bp.consume(n); 904   bp.consume(n);
905   } 905   }
HITCBC 906   54 } 906   54 }
907   907  
908   } // namespace capy 908   } // namespace capy
909   } // namespace boost 909   } // namespace boost
910   910  
911   #endif 911   #endif