LCOV - code coverage report
Current view: top level - capy/io - write_now.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 100.0 % 77 77
Test Date: 2026-10-17 14:10:44 Functions: 97.6 % 82 80 2

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

Generated by: LCOV version 2.3