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