89.11% Lines (90/101) 82.61% Functions (19/23)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2 - // Copyright (c) 2026 Michael Vandeberg  
3   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) 2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
4   // 3   //
5   // Distributed under the Boost Software License, Version 1.0. (See accompanying 4   // 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) 5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7   // 6   //
8   // Official repository: https://github.com/cppalliance/capy 7   // Official repository: https://github.com/cppalliance/capy
9   // 8   //
10   9  
11   #ifndef BOOST_CAPY_TEST_WRITE_SINK_HPP 10   #ifndef BOOST_CAPY_TEST_WRITE_SINK_HPP
12   #define BOOST_CAPY_TEST_WRITE_SINK_HPP 11   #define BOOST_CAPY_TEST_WRITE_SINK_HPP
13   12  
14   #include <boost/capy/detail/config.hpp> 13   #include <boost/capy/detail/config.hpp>
15   #include <boost/capy/buffers.hpp> 14   #include <boost/capy/buffers.hpp>
16   #include <boost/capy/buffers/buffer_copy.hpp> 15   #include <boost/capy/buffers/buffer_copy.hpp>
17   #include <boost/capy/buffers/make_buffer.hpp> 16   #include <boost/capy/buffers/make_buffer.hpp>
18   #include <coroutine> 17   #include <coroutine>
19   #include <boost/capy/ex/io_env.hpp> 18   #include <boost/capy/ex/io_env.hpp>
20   #include <boost/capy/io_result.hpp> 19   #include <boost/capy/io_result.hpp>
21   #include <boost/capy/error.hpp> 20   #include <boost/capy/error.hpp>
22   #include <boost/capy/test/fuse.hpp> 21   #include <boost/capy/test/fuse.hpp>
23   22  
24   #include <algorithm> 23   #include <algorithm>
  24 + #include <stop_token>
25   #include <string> 25   #include <string>
26   #include <string_view> 26   #include <string_view>
27   27  
28   namespace boost { 28   namespace boost {
29   namespace capy { 29   namespace capy {
30   namespace test { 30   namespace test {
31   31  
32   /** A mock sink for testing write operations. 32   /** A mock sink for testing write operations.
33   33  
34   Use this to verify code that performs complete writes without needing 34   Use this to verify code that performs complete writes without needing
35   real I/O. Call @ref write to write data, then @ref data to retrieve 35   real I/O. Call @ref write to write data, then @ref data to retrieve
36   what was written. The associated @ref fuse enables error injection 36   what was written. The associated @ref fuse enables error injection
37   at controlled points. 37   at controlled points.
38   38  
39   This class satisfies the @ref WriteSink concept by providing partial 39   This class satisfies the @ref WriteSink concept by providing partial
40   writes via `write_some` (satisfying @ref WriteStream), complete 40   writes via `write_some` (satisfying @ref WriteStream), complete
41   writes via `write`, and EOF signaling via `write_eof`. 41   writes via `write`, and EOF signaling via `write_eof`.
42   42  
43   @par Thread Safety 43   @par Thread Safety
44   Not thread-safe. 44   Not thread-safe.
45   45  
46   @par Example 46   @par Example
47   @code 47   @code
48   fuse f; 48   fuse f;
49   write_sink ws( f ); 49   write_sink ws( f );
50   50  
51   auto r = f.armed( [&]( fuse& ) -> task<void> { 51   auto r = f.armed( [&]( fuse& ) -> task<void> {
52   auto [ec, n] = co_await ws.write( 52   auto [ec, n] = co_await ws.write(
53   const_buffer( "Hello", 5 ) ); 53   const_buffer( "Hello", 5 ) );
54   if( ec ) 54   if( ec )
55   co_return; 55   co_return;
56   auto [ec2] = co_await ws.write_eof(); 56   auto [ec2] = co_await ws.write_eof();
57   if( ec2 ) 57   if( ec2 )
58   co_return; 58   co_return;
59   // ws.data() returns "Hello" 59   // ws.data() returns "Hello"
60   } ); 60   } );
61   @endcode 61   @endcode
62   62  
63   @see fuse, WriteSink 63   @see fuse, WriteSink
64   */ 64   */
65   class write_sink 65   class write_sink
66   { 66   {
67   fuse f_; 67   fuse f_;
68   std::string data_; 68   std::string data_;
69   std::string expect_; 69   std::string expect_;
70   std::size_t max_write_size_; 70   std::size_t max_write_size_;
71   bool eof_called_ = false; 71   bool eof_called_ = false;
72   72  
73   std::error_code 73   std::error_code
HITCBC 74   236 consume_match_() noexcept 74   236 consume_match_() noexcept
75   { 75   {
HITCBC 76   236 if(data_.empty() || expect_.empty()) 76   236 if(data_.empty() || expect_.empty())
HITCBC 77   228 return {}; 77   228 return {};
HITCBC 78   8 std::size_t const n = (std::min)(data_.size(), expect_.size()); 78   8 std::size_t const n = (std::min)(data_.size(), expect_.size());
HITCBC 79   8 if(std::string_view(data_.data(), n) != 79   8 if(std::string_view(data_.data(), n) !=
HITCBC 80   16 std::string_view(expect_.data(), n)) 80   16 std::string_view(expect_.data(), n))
HITCBC 81   4 return error::test_failure; 81   4 return error::test_failure;
HITCBC 82   4 data_.erase(0, n); 82   4 data_.erase(0, n);
HITCBC 83   4 expect_.erase(0, n); 83   4 expect_.erase(0, n);
HITCBC 84   4 return {}; 84   4 return {};
85   } 85   }
86   86  
87   public: 87   public:
88   /** Construct a write sink. 88   /** Construct a write sink.
89   89  
90   @param f The fuse used to inject errors during writes. 90   @param f The fuse used to inject errors during writes.
91   91  
92   @param max_write_size Maximum bytes transferred per write. 92   @param max_write_size Maximum bytes transferred per write.
93   Use to simulate chunked delivery. 93   Use to simulate chunked delivery.
94   */ 94   */
HITCBC 95   416 explicit write_sink( 95   412 explicit write_sink(
96   fuse f = {}, 96   fuse f = {},
97   std::size_t max_write_size = std::size_t(-1)) noexcept 97   std::size_t max_write_size = std::size_t(-1)) noexcept
HITCBC 98   416 : f_(std::move(f)) 98   412 : f_(std::move(f))
HITCBC 99   416 , max_write_size_(max_write_size) 99   412 , max_write_size_(max_write_size)
100   { 100   {
HITCBC 101   416 } 101   412 }
102   102  
103   /// Return the written data as a string view. 103   /// Return the written data as a string view.
104   std::string_view 104   std::string_view
HITCBC 105   100 data() const noexcept 105   100 data() const noexcept
106   { 106   {
HITCBC 107   100 return data_; 107   100 return data_;
108   } 108   }
109   109  
110   /** Set the expected data for subsequent writes. 110   /** Set the expected data for subsequent writes.
111   111  
112   Stores the expected data and immediately tries to match 112   Stores the expected data and immediately tries to match
113   against any data already written. Matched data is consumed 113   against any data already written. Matched data is consumed
114   from both buffers. 114   from both buffers.
115   115  
116   @param sv The expected data. 116   @param sv The expected data.
117   117  
118   @return An error if existing data does not match. 118   @return An error if existing data does not match.
119   */ 119   */
120   std::error_code 120   std::error_code
HITCBC 121   16 expect(std::string_view sv) 121   16 expect(std::string_view sv)
122   { 122   {
HITCBC 123   16 expect_.assign(sv); 123   16 expect_.assign(sv);
HITCBC 124   16 return consume_match_(); 124   16 return consume_match_();
125   } 125   }
126   126  
127   /// Return the number of bytes written. 127   /// Return the number of bytes written.
128   std::size_t 128   std::size_t
HITCBC 129   9 size() const noexcept 129   6 size() const noexcept
130   { 130   {
HITCBC 131   9 return data_.size(); 131   6 return data_.size();
132   } 132   }
133   133  
134   /// Return whether write_eof has been called. 134   /// Return whether write_eof has been called.
135   bool 135   bool
HITCBC 136   66 eof_called() const noexcept 136   64 eof_called() const noexcept
137   { 137   {
HITCBC 138   66 return eof_called_; 138   64 return eof_called_;
139   } 139   }
140   140  
141   /// Clear all data and reset state. 141   /// Clear all data and reset state.
142   void 142   void
HITCBC 143   4 clear() noexcept 143   4 clear() noexcept
144   { 144   {
HITCBC 145   4 data_.clear(); 145   4 data_.clear();
HITCBC 146   4 expect_.clear(); 146   4 expect_.clear();
HITCBC 147   4 eof_called_ = false; 147   4 eof_called_ = false;
HITCBC 148   4 } 148   4 }
149   149  
150   /** Asynchronously write some data to the sink. 150   /** Asynchronously write some data to the sink.
151   151  
152   Transfers up to `buffer_size( buffers )` bytes from the provided 152   Transfers up to `buffer_size( buffers )` bytes from the provided
153   const buffer sequence to the internal buffer. Before every write, 153   const buffer sequence to the internal buffer. Before every write,
154   the attached @ref fuse is consulted to possibly inject an error. 154   the attached @ref fuse is consulted to possibly inject an error.
155   155  
156   @param buffers The const buffer sequence containing data to write. 156   @param buffers The const buffer sequence containing data to write.
157   157  
158   @return An awaitable that await-returns `(error_code,std::size_t)`. 158   @return An awaitable that await-returns `(error_code,std::size_t)`.
159 - @par Cancellation  
160 - If the environment's stop token has been requested, the write  
161 - completes immediately with `error::canceled` and transfers no  
162 - data. An empty buffer sequence is a no-op that completes  
163 - successfully regardless of the stop token.  
164 -  
165   159  
166   @see fuse 160   @see fuse
167   */ 161   */
168   template<ConstBufferSequence CB> 162   template<ConstBufferSequence CB>
169   auto 163   auto
HITCBC 170   77 write_some(CB buffers) 164   76 write_some(CB buffers)
171   { 165   {
172   struct awaitable 166   struct awaitable
173   { 167   {
174   write_sink* self_; 168   write_sink* self_;
175 - bool canceled_ = false;  
176   CB buffers_; 169   CB buffers_;
177   170  
HITCBC 178 - 77 bool await_ready() const noexcept { return false; } 171 + 76 bool await_ready() const noexcept { return true; }
179   172  
MISUIC 180 - // The operation completes synchronously, but await_suspend is 173 + void await_suspend(
181 - // the only place io_env is delivered (the promise's  
182 - // transform_awaiter forwards it here). Returning false means  
183 - // the coroutine does not actually suspend; it resumes  
184 - // immediately, having observed the stop token. See io_env,  
185 - // IoAwaitable.  
186 - bool  
DCB 187 - 77 await_suspend(  
188   std::coroutine_handle<>, 174   std::coroutine_handle<>,
189 - io_env const* env) noexcept 175 + io_env const*) const noexcept
190 - canceled_ = env->stop_token.stop_requested();  
DCB 191 - 77 return false;  
ECB 192   77 { 176   {
MISUIC 193   } 177   }
194   178  
195   io_result<std::size_t> 179   io_result<std::size_t>
HITCBC 196   77 await_resume() 180   76 await_resume()
197   { 181   {
HITCBC 198   77 if(buffer_empty(buffers_)) 182   76 if(buffer_empty(buffers_))
HITCBC 199   2 return {{}, 0}; 183   2 return {{}, 0};
200 - if(canceled_)  
DCB 201 - 75 return {error::canceled, 0};  
DCB 202 - 1  
203   184  
HITCBC 204   74 auto ec = self_->f_.maybe_fail(); 185   74 auto ec = self_->f_.maybe_fail();
HITCBC 205   53 if(ec) 186   53 if(ec)
HITCBC 206   21 return {ec, 0}; 187   21 return {ec, 0};
207   188  
HITCBC 208   32 std::size_t n = buffer_size(buffers_); 189   32 std::size_t n = buffer_size(buffers_);
HITCBC 209   32 n = (std::min)(n, self_->max_write_size_); 190   32 n = (std::min)(n, self_->max_write_size_);
210   191  
HITCBC 211   32 std::size_t const old_size = self_->data_.size(); 192   32 std::size_t const old_size = self_->data_.size();
HITCBC 212   32 self_->data_.resize(old_size + n); 193   32 self_->data_.resize(old_size + n);
HITCBC 213   32 buffer_copy(make_buffer( 194   32 buffer_copy(make_buffer(
HITCBC 214   32 self_->data_.data() + old_size, n), buffers_, n); 195   32 self_->data_.data() + old_size, n), buffers_, n);
215   196  
HITCBC 216   32 ec = self_->consume_match_(); 197   32 ec = self_->consume_match_();
HITCBC 217   32 if(ec) 198   32 if(ec)
218   { 199   {
MISUBC 219   self_->data_.resize(old_size); 200   self_->data_.resize(old_size);
MISUBC 220   return {ec, 0}; 201   return {ec, 0};
221   } 202   }
222   203  
HITCBC 223   32 return {{}, n}; 204   32 return {{}, n};
224   } 205   }
225   }; 206   };
HITCBC 226   77 return awaitable{this, buffers}; 207   76 return awaitable{this, buffers};
227   } 208   }
228   209  
229   /** Asynchronously write data to the sink. 210   /** Asynchronously write data to the sink.
230   211  
231   Transfers all bytes from the provided const buffer sequence 212   Transfers all bytes from the provided const buffer sequence
232   to the internal buffer. Unlike @ref write_some, this ignores 213   to the internal buffer. Unlike @ref write_some, this ignores
233   `max_write_size` and writes all available data, matching the 214   `max_write_size` and writes all available data, matching the
234   @ref WriteSink semantic contract. 215   @ref WriteSink semantic contract.
235   216  
  217 + @par Exception Safety
  218 + Injected I/O conditions are reported via the `error_code`
  219 + component of the result. Throws `std::system_error` only when
  220 + the attached @ref fuse is in exception mode and reaches its
  221 + failure point; no-throw otherwise.
  222 +
236   @param buffers The const buffer sequence containing data to write. 223   @param buffers The const buffer sequence containing data to write.
237   224  
238   @return An awaitable that await-returns `(error_code,std::size_t)`. 225   @return An awaitable that await-returns `(error_code,std::size_t)`.
239   226  
240 - @par Cancellation 227 + @throws std::system_error When the attached @ref fuse is in
241 - If the environment's stop token has been requested, the write 228 + exception mode and reaches its failure point.
242 - completes immediately with `error::canceled` and transfers no  
243 - data.  
244   229  
245   @see fuse 230   @see fuse
246   */ 231   */
247   template<ConstBufferSequence CB> 232   template<ConstBufferSequence CB>
248   auto 233   auto
HITCBC 249   303 write(CB buffers) 234   302 write(CB buffers)
250   { 235   {
251   struct awaitable 236   struct awaitable
252   { 237   {
253   write_sink* self_; 238   write_sink* self_;
254 - bool canceled_ = false;  
255   CB buffers_; 239   CB buffers_;
256   240  
HITCBC 257 - 303 bool await_ready() const noexcept { return false; } 241 + 302 bool await_ready() const noexcept { return true; }
258   242  
MISUIC 259 - // Reads the stop token without suspending; see the comment 243 + void await_suspend(
260 - // on write_some() for details.  
261 - bool  
DCB 262 - 303 await_suspend(  
263   std::coroutine_handle<>, 244   std::coroutine_handle<>,
264 - io_env const* env) noexcept 245 + io_env const*) const noexcept
265 - canceled_ = env->stop_token.stop_requested();  
DCB 266 - 303 return false;  
ECB 267   303 { 246   {
MISUIC 268   } 247   }
269   248  
270   io_result<std::size_t> 249   io_result<std::size_t>
HITCBC 271   303 await_resume() 250   302 await_resume()
272 - if(canceled_)  
DCB 273 - 303 return {error::canceled, 0};  
DCB 274 - 1  
275   { 251   {
HITCBC 276   302 auto ec = self_->f_.maybe_fail(); 252   302 auto ec = self_->f_.maybe_fail();
HITCBC 277   241 if(ec) 253   241 if(ec)
HITCBC 278   61 return {ec, 0}; 254   61 return {ec, 0};
279   255  
HITCBC 280   180 std::size_t n = buffer_size(buffers_); 256   180 std::size_t n = buffer_size(buffers_);
HITCBC 281   180 if(n == 0) 257   180 if(n == 0)
HITCBC 282   2 return {{}, 0}; 258   2 return {{}, 0};
283   259  
HITCBC 284   178 std::size_t const old_size = self_->data_.size(); 260   178 std::size_t const old_size = self_->data_.size();
HITCBC 285   178 self_->data_.resize(old_size + n); 261   178 self_->data_.resize(old_size + n);
HITCBC 286   178 buffer_copy(make_buffer( 262   178 buffer_copy(make_buffer(
HITCBC 287   178 self_->data_.data() + old_size, n), buffers_); 263   178 self_->data_.data() + old_size, n), buffers_);
288   264  
HITCBC 289   178 ec = self_->consume_match_(); 265   178 ec = self_->consume_match_();
HITCBC 290   178 if(ec) 266   178 if(ec)
HITCBC 291   2 return {ec, n}; 267   2 return {ec, n};
292   268  
HITCBC 293   176 return {{}, n}; 269   176 return {{}, n};
294   } 270   }
295   }; 271   };
HITCBC 296   303 return awaitable{this, buffers}; 272   302 return awaitable{this, buffers};
297   } 273   }
298   274  
299   /** Atomically write data and signal end-of-stream. 275   /** Atomically write data and signal end-of-stream.
300   276  
301   Transfers all bytes from the provided const buffer sequence to 277   Transfers all bytes from the provided const buffer sequence to
302   the internal buffer and signals end-of-stream. Before the write, 278   the internal buffer and signals end-of-stream. Before the write,
303   the attached @ref fuse is consulted to possibly inject an error 279   the attached @ref fuse is consulted to possibly inject an error
304   for testing fault scenarios. 280   for testing fault scenarios.
305   281  
306   @par Effects 282   @par Effects
307   On success, appends the written bytes to the internal buffer 283   On success, appends the written bytes to the internal buffer
308   and marks the sink as finalized. 284   and marks the sink as finalized.
309   If an error is injected by the fuse, the internal buffer remains 285   If an error is injected by the fuse, the internal buffer remains
310   unchanged. 286   unchanged.
311   287  
312   @par Exception Safety 288   @par Exception Safety
313 - No-throw guarantee. 289 + Injected I/O conditions are reported via the `error_code`
314 - 290 + component of the result. Throws `std::system_error` only when
315 - @par Cancellation 291 + the attached @ref fuse is in exception mode and reaches its
316 - If the environment's stop token has been requested, the operation 292 + failure point; no-throw otherwise.
317 - completes immediately with `error::canceled`, transfers no data,  
318 - and does not signal end-of-stream.  
319   293  
320   @param buffers The const buffer sequence containing data to write. 294   @param buffers The const buffer sequence containing data to write.
321   295  
322   @return An awaitable that await-returns `(error_code,std::size_t)`. 296   @return An awaitable that await-returns `(error_code,std::size_t)`.
323   297  
  298 + @throws std::system_error When the attached @ref fuse is in
  299 + exception mode and reaches its failure point.
  300 +
324   @see fuse 301   @see fuse
325   */ 302   */
326   template<ConstBufferSequence CB> 303   template<ConstBufferSequence CB>
327   auto 304   auto
HITCBC 328   35 write_eof(CB buffers) 305   34 write_eof(CB buffers)
329   { 306   {
330   struct awaitable 307   struct awaitable
331   { 308   {
332   write_sink* self_; 309   write_sink* self_;
333 - bool canceled_ = false;  
334   CB buffers_; 310   CB buffers_;
335   311  
HITCBC 336 - 35 bool await_ready() const noexcept { return false; } 312 + 34 bool await_ready() const noexcept { return true; }
337   313  
MISUIC 338 - // Reads the stop token without suspending; see the comment 314 + void await_suspend(
339 - // on write_some() for details.  
340 - bool  
DCB 341 - 35 await_suspend(  
342   std::coroutine_handle<>, 315   std::coroutine_handle<>,
343 - io_env const* env) noexcept 316 + io_env const*) const noexcept
344 - canceled_ = env->stop_token.stop_requested();  
DCB 345 - 35 return false;  
ECB 346   35 { 317   {
MISUIC 347   } 318   }
348   319  
349   io_result<std::size_t> 320   io_result<std::size_t>
HITCBC 350   35 await_resume() 321   34 await_resume()
351 - if(canceled_)  
DCB 352 - 35 return {error::canceled, 0};  
DCB 353 - 1  
354   { 322   {
HITCBC 355   34 auto ec = self_->f_.maybe_fail(); 323   34 auto ec = self_->f_.maybe_fail();
HITCBC 356   23 if(ec) 324   23 if(ec)
HITCBC 357   11 return {ec, 0}; 325   11 return {ec, 0};
358   326  
HITCBC 359   12 std::size_t n = buffer_size(buffers_); 327   12 std::size_t n = buffer_size(buffers_);
HITCBC 360   12 if(n > 0) 328   12 if(n > 0)
361   { 329   {
HITCBC 362   10 std::size_t const old_size = self_->data_.size(); 330   10 std::size_t const old_size = self_->data_.size();
HITCBC 363   10 self_->data_.resize(old_size + n); 331   10 self_->data_.resize(old_size + n);
HITCBC 364   10 buffer_copy(make_buffer( 332   10 buffer_copy(make_buffer(
HITCBC 365   10 self_->data_.data() + old_size, n), buffers_); 333   10 self_->data_.data() + old_size, n), buffers_);
366   334  
HITCBC 367   10 ec = self_->consume_match_(); 335   10 ec = self_->consume_match_();
HITCBC 368   10 if(ec) 336   10 if(ec)
MISUBC 369   return {ec, n}; 337   return {ec, n};
370   } 338   }
371   339  
HITCBC 372   12 self_->eof_called_ = true; 340   12 self_->eof_called_ = true;
373   341  
HITCBC 374   12 return {{}, n}; 342   12 return {{}, n};
375   } 343   }
376   }; 344   };
HITCBC 377   35 return awaitable{this, buffers}; 345   34 return awaitable{this, buffers};
378   } 346   }
379   347  
380   /** Signal end-of-stream. 348   /** Signal end-of-stream.
381   349  
382   Marks the sink as finalized, indicating no more data will be 350   Marks the sink as finalized, indicating no more data will be
383   written. Before signaling, the attached @ref fuse is consulted 351   written. Before signaling, the attached @ref fuse is consulted
384   to possibly inject an error for testing fault scenarios. 352   to possibly inject an error for testing fault scenarios.
385   353  
386   @par Effects 354   @par Effects
387   On success, marks the sink as finalized. 355   On success, marks the sink as finalized.
388   If an error is injected by the fuse, the state remains unchanged. 356   If an error is injected by the fuse, the state remains unchanged.
389   357  
390   @par Exception Safety 358   @par Exception Safety
391 - No-throw guarantee. 359 + Injected I/O conditions are reported via the `error_code`
392 - 360 + component of the result. Throws `std::system_error` only when
393 - @par Cancellation 361 + the attached @ref fuse is in exception mode and reaches its
394 - If the environment's stop token has been requested, the operation 362 + failure point; no-throw otherwise.
395 - completes immediately with `error::canceled` and does not signal  
396 - end-of-stream.  
397   363  
398   @return An awaitable that await-returns `(error_code)`. 364   @return An awaitable that await-returns `(error_code)`.
399   365  
  366 + @throws std::system_error When the attached @ref fuse is in
  367 + exception mode and reaches its failure point.
  368 +
400   @see fuse 369   @see fuse
401   */ 370   */
402   auto 371   auto
HITCBC 403   83 write_eof() 372   82 write_eof()
404   { 373   {
405   struct awaitable 374   struct awaitable
406   { 375   {
407 - bool canceled_ = false;  
408   write_sink* self_; 376   write_sink* self_;
409   377  
HITCBC 410 - 83 bool await_ready() const noexcept { return false; } 378 + 82 bool await_ready() const noexcept { return true; }
411   379  
412 - // Reads the stop token without suspending; see the comment 380 + // This method is required to satisfy Capy's IoAwaitable concept,
413 - // on write_some() for details. 381 + // but is never called because await_ready() returns true.
414 - bool 382 + // See the comment on write(CB buffers) for a detailed explanation.
MISLBC 415 - 83 await_suspend( 383 + void await_suspend(
416   std::coroutine_handle<>, 384   std::coroutine_handle<>,
417 - io_env const* env) noexcept 385 + io_env const*) const noexcept
418 - canceled_ = env->stop_token.stop_requested();  
DCB 419 - 83 return false;  
ECB 420   83 { 386   {
MISUIC 421   } 387   }
422   388  
423   io_result<> 389   io_result<>
HITCBC 424   83 await_resume() 390   82 await_resume()
425 - if(canceled_)  
DCB 426 - 83 return {error::canceled};  
DCB 427 - 1  
428   { 391   {
HITCBC 429   82 auto ec = self_->f_.maybe_fail(); 392   82 auto ec = self_->f_.maybe_fail();
HITCBC 430   60 if(ec) 393   60 if(ec)
HITCBC 431   22 return {ec}; 394   22 return {ec};
432   395  
HITCBC 433   38 self_->eof_called_ = true; 396   38 self_->eof_called_ = true;
HITCBC 434   38 return {}; 397   38 return {};
435   } 398   }
436   }; 399   };
HITCBC 437   83 return awaitable{this}; 400   82 return awaitable{this};
438   } 401   }
439   }; 402   };
440   403  
441   } // test 404   } // test
442   } // capy 405   } // capy
443   } // boost 406   } // boost
444   407  
445   #endif 408   #endif