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