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