100.00% Lines (77/77) 100.00% Functions (22/22)
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_WRITE_NOW_HPP 11   #ifndef BOOST_CAPY_IO_WRITE_NOW_HPP
12   #define BOOST_CAPY_IO_WRITE_NOW_HPP 12   #define BOOST_CAPY_IO_WRITE_NOW_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/buffers/consuming_buffers.hpp> 17   #include <boost/capy/buffers/consuming_buffers.hpp>
18   #include <boost/capy/concept/context_awaitable.hpp> 18   #include <boost/capy/concept/context_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/executor_ref.hpp> 21   #include <boost/capy/ex/executor_ref.hpp>
22   #include <boost/capy/ex/frame_allocator.hpp> 22   #include <boost/capy/ex/frame_allocator.hpp>
23   #include <boost/capy/ex/abandon.hpp> 23   #include <boost/capy/ex/abandon.hpp>
24   #include <boost/capy/ex/await_context.hpp> 24   #include <boost/capy/ex/await_context.hpp>
25   #include <boost/capy/results.hpp> 25   #include <boost/capy/results.hpp>
26   26  
27   #include <cstddef> 27   #include <cstddef>
28   #include <exception> 28   #include <exception>
29   #include <new> 29   #include <new>
30   #include <stop_token> 30   #include <stop_token>
31   #include <utility> 31   #include <utility>
32   32  
33   #ifndef BOOST_CAPY_WRITE_NOW_WORKAROUND 33   #ifndef BOOST_CAPY_WRITE_NOW_WORKAROUND
34   # if defined(__GNUC__) && !defined(__clang__) 34   # if defined(__GNUC__) && !defined(__clang__)
35   # define BOOST_CAPY_WRITE_NOW_WORKAROUND 1 35   # define BOOST_CAPY_WRITE_NOW_WORKAROUND 1
36   # else 36   # else
37   # define BOOST_CAPY_WRITE_NOW_WORKAROUND 0 37   # define BOOST_CAPY_WRITE_NOW_WORKAROUND 0
38   # endif 38   # endif
39   #endif 39   #endif
40   40  
41   namespace boost { 41   namespace boost {
42   namespace capy { 42   namespace capy {
43   43  
44   /** Eagerly writes complete buffer sequences with frame caching. 44   /** Eagerly writes complete buffer sequences with frame caching.
45   45  
46   This class wraps a @ref WriteStream and provides an `operator()` 46   This class wraps a @ref WriteStream and provides an `operator()`
47   that writes an entire buffer sequence, attempting to complete 47   that writes an entire buffer sequence, attempting to complete
48   synchronously. If every `write_some` completes without suspending, 48   synchronously. If every `write_some` completes without suspending,
49   the entire operation finishes in `await_ready` with no coroutine 49   the entire operation finishes in `await_ready` with no coroutine
50   suspension. 50   suspension.
51   51  
52   The class maintains a one-element coroutine frame cache. After 52   The class maintains a one-element coroutine frame cache. After
53   the first call, subsequent calls reuse the cached frame memory, 53   the first call, subsequent calls reuse the cached frame memory,
54   avoiding repeated allocation for the internal coroutine. 54   avoiding repeated allocation for the internal coroutine.
55   55  
56   @tparam Stream The stream type, must satisfy @ref WriteStream. 56   @tparam Stream The stream type, must satisfy @ref WriteStream.
57   57  
58   @par Thread Safety 58   @par Thread Safety
59   Distinct objects: Safe. 59   Distinct objects: Safe.
60   Shared objects: Unsafe. 60   Shared objects: Unsafe.
61   61  
62   @par Preconditions 62   @par Preconditions
63   Only one operation may be outstanding at a time. A new call to 63   Only one operation may be outstanding at a time. A new call to
64   `operator()` must not be made until the previous operation has 64   `operator()` must not be made until the previous operation has
65   completed (i.e., the returned awaitable is fully consumed). 65   completed (i.e., the returned awaitable is fully consumed).
66   66  
67   Destroying the returned operation object while it is being awaited 67   Destroying the returned operation object while it is being awaited
68   calls `std::terminate`, except while its chain is unwound after an 68   calls `std::terminate`, except while its chain is unwound after an
69   @ref abandon. 69   @ref abandon.
70   70  
71   @par Example 71   @par Example
72   72  
73   @code 73   @code
74   template< WriteStream Stream > 74   template< WriteStream Stream >
75   task<> send_messages( Stream& stream ) 75   task<> send_messages( Stream& stream )
76   { 76   {
77   write_now wn( stream ); 77   write_now wn( stream );
78   auto [ec1, n1] = co_await wn( make_buffer( "hello" ) ); 78   auto [ec1, n1] = co_await wn( make_buffer( "hello" ) );
79   if( ec1 ) 79   if( ec1 )
80   detail::throw_system_error( ec1 ); 80   detail::throw_system_error( ec1 );
81   auto [ec2, n2] = co_await wn( make_buffer( "world" ) ); 81   auto [ec2, n2] = co_await wn( make_buffer( "world" ) );
82   if( ec2 ) 82   if( ec2 )
83   detail::throw_system_error( ec2 ); 83   detail::throw_system_error( ec2 );
84   } 84   }
85   @endcode 85   @endcode
86   86  
87   @see @ref write, @ref write_some, @ref WriteStream, @ref ConstBufferSequence 87   @see @ref write, @ref write_some, @ref WriteStream, @ref ConstBufferSequence
88   */ 88   */
89   template<class Stream> 89   template<class Stream>
90   requires WriteStream<Stream> 90   requires WriteStream<Stream>
91   class write_now 91   class write_now
92   { 92   {
93   Stream& stream_; 93   Stream& stream_;
94   void* cached_frame_ = nullptr; 94   void* cached_frame_ = nullptr;
95   std::size_t cached_size_ = 0; 95   std::size_t cached_size_ = 0;
96   96  
97   struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE 97   struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE
98   op_type 98   op_type
99   { 99   {
100   struct promise_type 100   struct promise_type
101   { 101   {
102   results<std::size_t> result_; 102   results<std::size_t> result_;
103   std::exception_ptr ep_; 103   std::exception_ptr ep_;
104   std::coroutine_handle<> cont_{nullptr}; 104   std::coroutine_handle<> cont_{nullptr};
105   await_context const* env_ = nullptr; 105   await_context const* env_ = nullptr;
106   bool done_ = false; 106   bool done_ = false;
107   107  
108   // The awaiting op_type while a parent awaits this frame. 108   // The awaiting op_type while a parent awaits this frame.
109   op_type* owner_ = nullptr; 109   op_type* owner_ = nullptr;
110   110  
HITCBC 111   76 op_type get_return_object() 111   76 op_type get_return_object()
112   { 112   {
113   return op_type{ 113   return op_type{
114   std::coroutine_handle< 114   std::coroutine_handle<
HITCBC 115   76 promise_type>::from_promise(*this)}; 115   76 promise_type>::from_promise(*this)};
116   } 116   }
117   117  
HITCBC 118   76 auto initial_suspend() noexcept 118   76 auto initial_suspend() noexcept
119   { 119   {
120   #if BOOST_CAPY_WRITE_NOW_WORKAROUND 120   #if BOOST_CAPY_WRITE_NOW_WORKAROUND
HITCBC 121   76 return std::suspend_always{}; 121   76 return std::suspend_always{};
122   #else 122   #else
123   return std::suspend_never{}; 123   return std::suspend_never{};
124   #endif 124   #endif
125   } 125   }
126   126  
HITCBC 127   72 auto final_suspend() noexcept 127   72 auto final_suspend() noexcept
128   { 128   {
129   struct awaiter 129   struct awaiter
130   { 130   {
131   promise_type* p_; 131   promise_type* p_;
132   132  
HITCBC 133   72 bool await_ready() const noexcept 133   72 bool await_ready() const noexcept
134   { 134   {
HITCBC 135   72 return false; 135   72 return false;
136   } 136   }
137   137  
HITCBC 138   72 std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept 138   72 std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept
139   { 139   {
HITCBC 140   72 p_->done_ = true; 140   72 p_->done_ = true;
HITCBC 141   72 if(!p_->cont_) 141   72 if(!p_->cont_)
142   return std::noop_coroutine(); // LCOV_EXCL_LINE cont_ always set on this (suspend_always) path 142   return std::noop_coroutine(); // LCOV_EXCL_LINE cont_ always set on this (suspend_always) path
HITCBC 143   72 return p_->cont_; 143   72 return p_->cont_;
144   } 144   }
145   145  
146   void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed 146   void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed
147   }; 147   };
HITCBC 148   72 return awaiter{this}; 148   72 return awaiter{this};
149   } 149   }
150   150  
HITCBC 151   50 void return_value( 151   50 void return_value(
152   results<std::size_t> r) noexcept 152   results<std::size_t> r) noexcept
153   { 153   {
HITCBC 154   50 result_ = r; 154   50 result_ = r;
HITCBC 155   50 } 155   50 }
156   156  
HITCBC 157   22 void unhandled_exception() 157   22 void unhandled_exception()
158   { 158   {
HITCBC 159   22 ep_ = std::current_exception(); 159   22 ep_ = std::current_exception();
HITCBC 160   22 } 160   22 }
161   161  
162   std::suspend_always yield_value(int) noexcept 162   std::suspend_always yield_value(int) noexcept
163   { 163   {
164   return {}; 164   return {};
165   } 165   }
166   166  
167   template<class A> 167   template<class A>
HITCBC 168   93 auto await_transform(A&& a) 168   93 auto await_transform(A&& a)
169   { 169   {
170   using decayed = std::decay_t<A>; 170   using decayed = std::decay_t<A>;
171   if constexpr (ContextAwaitable<decayed>) 171   if constexpr (ContextAwaitable<decayed>)
172   { 172   {
173   struct wrapper 173   struct wrapper
174   { 174   {
175   decayed inner_; 175   decayed inner_;
176   promise_type* p_; 176   promise_type* p_;
177   177  
HITCBC 178   93 bool await_ready() 178   93 bool await_ready()
179   { 179   {
HITCBC 180   93 return inner_.await_ready(); 180   93 return inner_.await_ready();
181   } 181   }
182   182  
183   // The inner operation may resume or destroy this 183   // The inner operation may resume or destroy this
184   // frame on another thread before control returns 184   // frame on another thread before control returns
185   // here, so the handle must not round-trip through 185   // here, so the handle must not round-trip through
186   // the frame. 186   // the frame.
HITCBC 187   93 auto await_suspend(std::coroutine_handle<> h) 187   93 auto await_suspend(std::coroutine_handle<> h)
188   { 188   {
HITCBC 189   93 return detail::symmetric_transfer( 189   93 return detail::symmetric_transfer(
190   detail::call_await_suspend( 190   detail::call_await_suspend(
191   &inner_, h, 191   &inner_, h,
HITCBC 192   186 p_->env_)); 192   186 p_->env_));
193   } 193   }
194   194  
HITCBC 195   91 decltype(auto) await_resume() 195   91 decltype(auto) await_resume()
196   { 196   {
197   // The resumption may arrive on another thread. 197   // The resumption may arrive on another thread.
HITCBC 198   91 set_current_frame_allocator( 198   91 set_current_frame_allocator(
HITCBC 199   91 p_->env_->frame_allocator); 199   91 p_->env_->frame_allocator);
HITCBC 200   91 return inner_.await_resume(); 200   91 return inner_.await_resume();
201   } 201   }
202   }; 202   };
203   return wrapper{ 203   return wrapper{
HITCBC 204   93 std::forward<A>(a), this}; 204   93 std::forward<A>(a), this};
205   } 205   }
206   else 206   else
207   { 207   {
208   return std::forward<A>(a); 208   return std::forward<A>(a);
209   } 209   }
210   } 210   }
211   211  
212   static void* 212   static void*
HITCBC 213   76 operator new( 213   76 operator new(
214   std::size_t size, 214   std::size_t size,
215   write_now& self, 215   write_now& self,
216   auto&) 216   auto&)
217   { 217   {
HITCBC 218   76 if(self.cached_frame_ && 218   76 if(self.cached_frame_ &&
HITCBC 219   6 self.cached_size_ >= size) 219   6 self.cached_size_ >= size)
HITCBC 220   5 return self.cached_frame_; 220   5 return self.cached_frame_;
HITCBC 221   71 void* p = ::operator new(size); 221   71 void* p = ::operator new(size);
HITCBC 222   71 if(self.cached_frame_) 222   71 if(self.cached_frame_)
HITCBC 223   1 ::operator delete(self.cached_frame_); 223   1 ::operator delete(self.cached_frame_);
HITCBC 224   71 self.cached_frame_ = p; 224   71 self.cached_frame_ = p;
HITCBC 225   71 self.cached_size_ = size; 225   71 self.cached_size_ = size;
HITCBC 226   71 return p; 226   71 return p;
227   } 227   }
228   228  
229   static void 229   static void
HITCBC 230   76 operator delete(void*, std::size_t) noexcept 230   76 operator delete(void*, std::size_t) noexcept
231   { 231   {
HITCBC 232   76 } 232   76 }
233   }; 233   };
234   234  
235   std::coroutine_handle<promise_type> h_; 235   std::coroutine_handle<promise_type> h_;
236   236  
HITCBC 237   150 ~op_type() 237   150 ~op_type()
238   { 238   {
HITCBC 239   150 if(h_) 239   150 if(h_)
240   { 240   {
241   // Only the unwind of an abandoned chain destroys a frame 241   // Only the unwind of an abandoned chain destroys a frame
242   // that is still awaited. 242   // that is still awaited.
HITCBC 243   76 auto& p = h_.promise(); 243   76 auto& p = h_.promise();
HITCBC 244   78 if(!p.done_ && p.owner_ && 244   78 if(!p.done_ && p.owner_ &&
HITCBC 245   2 !(p.env_->owner && p.env_->owner->abandoned())) 245   2 !(p.env_->owner && p.env_->owner->abandoned()))
246   std::terminate(); // LCOV_EXCL_LINE reached only in a death test's child process 246   std::terminate(); // LCOV_EXCL_LINE reached only in a death test's child process
HITCBC 247   76 h_.destroy(); 247   76 h_.destroy();
248   } 248   }
HITCBC 249   150 } 249   150 }
250   250  
251   op_type(op_type const&) = delete; 251   op_type(op_type const&) = delete;
252   op_type& operator=(op_type const&) = delete; 252   op_type& operator=(op_type const&) = delete;
253   253  
HITCBC 254   74 op_type(op_type&& other) noexcept 254   74 op_type(op_type&& other) noexcept
HITCBC 255   74 : h_(std::exchange(other.h_, nullptr)) 255   74 : h_(std::exchange(other.h_, nullptr))
256   { 256   {
HITCBC 257   74 } 257   74 }
258   258  
259   op_type& operator=(op_type&&) = delete; 259   op_type& operator=(op_type&&) = delete;
260   260  
HITCBC 261   74 bool await_ready() const noexcept 261   74 bool await_ready() const noexcept
262   { 262   {
HITCBC 263   74 return h_.promise().done_; 263   74 return h_.promise().done_;
264   } 264   }
265   265  
HITCBC 266   74 std::coroutine_handle<> await_suspend( 266   74 std::coroutine_handle<> await_suspend(
267   std::coroutine_handle<> cont, 267   std::coroutine_handle<> cont,
268   await_context const* env) 268   await_context const* env)
269   { 269   {
HITCBC 270   74 auto& p = h_.promise(); 270   74 auto& p = h_.promise();
HITCBC 271   74 p.cont_ = cont; 271   74 p.cont_ = cont;
HITCBC 272   74 p.env_ = env; 272   74 p.env_ = env;
HITCBC 273   74 p.owner_ = this; 273   74 p.owner_ = this;
HITCBC 274   74 return h_; 274   74 return h_;
275   } 275   }
276   276  
HITCBC 277   72 [[nodiscard]] results<std::size_t> await_resume() 277   72 [[nodiscard]] results<std::size_t> await_resume()
278   { 278   {
HITCBC 279   72 auto& p = h_.promise(); 279   72 auto& p = h_.promise();
HITCBC 280   72 p.owner_ = nullptr; 280   72 p.owner_ = nullptr;
HITCBC 281   72 if(p.ep_) 281   72 if(p.ep_)
HITCBC 282   22 std::rethrow_exception(p.ep_); 282   22 std::rethrow_exception(p.ep_);
HITCBC 283   50 return p.result_; 283   50 return p.result_;
284   } 284   }
285   285  
286   private: 286   private:
HITCBC 287   76 explicit op_type( 287   76 explicit op_type(
288   std::coroutine_handle<promise_type> h) 288   std::coroutine_handle<promise_type> h)
HITCBC 289   76 : h_(h) 289   76 : h_(h)
290   { 290   {
HITCBC 291   76 } 291   76 }
292   }; 292   };
293   293  
294   public: 294   public:
295   /** Destructor. Frees the cached coroutine frame. */ 295   /** Destructor. Frees the cached coroutine frame. */
HITCBC 296   70 ~write_now() 296   70 ~write_now()
297   { 297   {
HITCBC 298   70 if(cached_frame_) 298   70 if(cached_frame_)
HITCBC 299   70 ::operator delete(cached_frame_); 299   70 ::operator delete(cached_frame_);
HITCBC 300   70 } 300   70 }
301   301  
302   /** Construct from a stream reference. 302   /** Construct from a stream reference.
303   303  
304   @param s The stream to write to. Must outlive this object. 304   @param s The stream to write to. Must outlive this object.
305   */ 305   */
306   explicit 306   explicit
HITCBC 307   70 write_now(Stream& s) noexcept 307   70 write_now(Stream& s) noexcept
HITCBC 308   70 : stream_(s) 308   70 : stream_(s)
309   { 309   {
HITCBC 310   70 } 310   70 }
311   311  
312   write_now(write_now const&) = delete; 312   write_now(write_now const&) = delete;
313   write_now& operator=(write_now const&) = delete; 313   write_now& operator=(write_now const&) = delete;
314   314  
315   /** Eagerly write the entire buffer sequence. 315   /** Eagerly write the entire buffer sequence.
316   316  
317   Writes data to the stream by calling `write_some` repeatedly 317   Writes data to the stream by calling `write_some` repeatedly
318   until the entire buffer sequence is written or an error 318   until the entire buffer sequence is written or an error
319   occurs. The operation attempts to complete synchronously: 319   occurs. The operation attempts to complete synchronously:
320   if every `write_some` completes without suspending, the 320   if every `write_some` completes without suspending, the
321   entire operation finishes in `await_ready`. 321   entire operation finishes in `await_ready`.
322   322  
323   When the fast path cannot complete, the coroutine suspends 323   When the fast path cannot complete, the coroutine suspends
324   and continues asynchronously. The internal coroutine frame 324   and continues asynchronously. The internal coroutine frame
325   is cached and reused across calls. 325   is cached and reused across calls.
326   326  
327   @param buffers The buffer sequence to write. Passed by 327   @param buffers The buffer sequence to write. Passed by
328   value to ensure the sequence lives in the coroutine 328   value to ensure the sequence lives in the coroutine
329   frame across suspension points. 329   frame across suspension points.
330   330  
331   @return An awaitable that await-returns `(error_code,std::size_t)`. 331   @return An awaitable that await-returns `(error_code,std::size_t)`.
332   On success, `n` equals `buffer_size(buffers)`. On 332   On success, `n` equals `buffer_size(buffers)`. On
333   error, `n` is the number of bytes written before the 333   error, `n` is the number of bytes written before the
334   error. Compare error codes to conditions: 334   error. Compare error codes to conditions:
335   @li `cond::canceled` - Operation was cancelled 335   @li `cond::canceled` - Operation was cancelled
336   @li `std::errc::broken_pipe` - Peer closed connection 336   @li `std::errc::broken_pipe` - Peer closed connection
337   337  
338   @par Example 338   @par Example
339   339  
340   @code 340   @code
341   write_now wn( stream ); 341   write_now wn( stream );
342   auto [ec, n] = co_await wn( make_buffer( body ) ); 342   auto [ec, n] = co_await wn( make_buffer( body ) );
343   if( ec ) 343   if( ec )
344   detail::throw_system_error( ec ); 344   detail::throw_system_error( ec );
345   @endcode 345   @endcode
346   346  
347   @see @ref write, @ref write_some, @ref WriteStream 347   @see @ref write, @ref write_some, @ref WriteStream
348   */ 348   */
349   // GCC falsely warns that the coroutine promise's 349   // GCC falsely warns that the coroutine promise's
350   // placement operator new(size_t, write_now&, auto&) 350   // placement operator new(size_t, write_now&, auto&)
351   // mismatches operator delete(void*, size_t). Per the 351   // mismatches operator delete(void*, size_t). Per the
352   // standard, coroutine deallocation lookup is separate. 352   // standard, coroutine deallocation lookup is separate.
353   #if defined(__GNUC__) && !defined(__clang__) 353   #if defined(__GNUC__) && !defined(__clang__)
354   #pragma GCC diagnostic push 354   #pragma GCC diagnostic push
355   #pragma GCC diagnostic ignored "-Wmismatched-new-delete" 355   #pragma GCC diagnostic ignored "-Wmismatched-new-delete"
356   #endif 356   #endif
357   357  
358   #if BOOST_CAPY_WRITE_NOW_WORKAROUND 358   #if BOOST_CAPY_WRITE_NOW_WORKAROUND
359   template<ConstBufferSequence Buffers> 359   template<ConstBufferSequence Buffers>
360   op_type 360   op_type
HITCBC 361   76 operator()(Buffers buffers) 361   76 operator()(Buffers buffers)
362   { 362   {
363   std::size_t const total_size = buffer_size(buffers); 363   std::size_t const total_size = buffer_size(buffers);
364   std::size_t total_written = 0; 364   std::size_t total_written = 0;
365   consuming_buffers cb(buffers); 365   consuming_buffers cb(buffers);
366   while(total_written < total_size) 366   while(total_written < total_size)
367   { 367   {
368   auto r = 368   auto r =
369   co_await stream_.write_some(cb.data()); 369   co_await stream_.write_some(cb.data());
370   cb.consume(std::get<1>(r)); 370   cb.consume(std::get<1>(r));
371   total_written += std::get<1>(r); 371   total_written += std::get<1>(r);
372   if(std::get<0>(r)) 372   if(std::get<0>(r))
373   co_return results<std::size_t>{ 373   co_return results<std::size_t>{
374   std::get<0>(r), total_written}; 374   std::get<0>(r), total_written};
375   } 375   }
376   co_return results<std::size_t>{ 376   co_return results<std::size_t>{
377   std::error_code(), total_written}; 377   std::error_code(), total_written};
HITCBC 378   152 } 378   152 }
379   #else 379   #else
380   template<ConstBufferSequence Buffers> 380   template<ConstBufferSequence Buffers>
381   op_type 381   op_type
382   operator()(Buffers buffers) 382   operator()(Buffers buffers)
383   { 383   {
384   std::size_t const total_size = buffer_size(buffers); 384   std::size_t const total_size = buffer_size(buffers);
385   std::size_t total_written = 0; 385   std::size_t total_written = 0;
386   386  
387   // GCC ICE in expand_expr_real_1 (expr.cc:11376) 387   // GCC ICE in expand_expr_real_1 (expr.cc:11376)
388   // when the buffer slice spans a co_yield, so 388   // when the buffer slice spans a co_yield, so
389   // the GCC path uses a separate simple coroutine. 389   // the GCC path uses a separate simple coroutine.
390   consuming_buffers cb(buffers); 390   consuming_buffers cb(buffers);
391   while(total_written < total_size) 391   while(total_written < total_size)
392   { 392   {
393   auto inner = stream_.write_some(cb.data()); 393   auto inner = stream_.write_some(cb.data());
394   if(!inner.await_ready()) 394   if(!inner.await_ready())
395   break; 395   break;
396   auto r = inner.await_resume(); 396   auto r = inner.await_resume();
397   if(std::get<0>(r)) 397   if(std::get<0>(r))
398   co_return results<std::size_t>{ 398   co_return results<std::size_t>{
399   std::get<0>(r), total_written}; 399   std::get<0>(r), total_written};
400   cb.consume(std::get<1>(r)); 400   cb.consume(std::get<1>(r));
401   total_written += std::get<1>(r); 401   total_written += std::get<1>(r);
402   } 402   }
403   403  
404   if(total_written >= total_size) 404   if(total_written >= total_size)
405   co_return results<std::size_t>{ 405   co_return results<std::size_t>{
406   std::error_code(), total_written}; 406   std::error_code(), total_written};
407   407  
408   co_yield 0; 408   co_yield 0;
409   409  
410   while(total_written < total_size) 410   while(total_written < total_size)
411   { 411   {
412   auto r = 412   auto r =
413   co_await stream_.write_some(cb.data()); 413   co_await stream_.write_some(cb.data());
414   cb.consume(std::get<1>(r)); 414   cb.consume(std::get<1>(r));
415   total_written += std::get<1>(r); 415   total_written += std::get<1>(r);
416   if(std::get<0>(r)) 416   if(std::get<0>(r))
417   co_return results<std::size_t>{ 417   co_return results<std::size_t>{
418   std::get<0>(r), total_written}; 418   std::get<0>(r), total_written};
419   } 419   }
420   co_return results<std::size_t>{ 420   co_return results<std::size_t>{
421   std::error_code(), total_written}; 421   std::error_code(), total_written};
422   } 422   }
423   #endif 423   #endif
424   424  
425   #if defined(__GNUC__) && !defined(__clang__) 425   #if defined(__GNUC__) && !defined(__clang__)
426   #pragma GCC diagnostic pop 426   #pragma GCC diagnostic pop
427   #endif 427   #endif
428   }; 428   };
429   429  
430   template<WriteStream S> 430   template<WriteStream S>
431   write_now(S&) -> write_now<S>; 431   write_now(S&) -> write_now<S>;
432   432  
433   } // namespace capy 433   } // namespace capy
434   } // namespace boost 434   } // namespace boost
435   435  
436   #endif 436   #endif