LCOV - code coverage report
Current view: top level - corosio/detail - buffer_param.hpp (source / functions) Coverage Total Hit
Test: coverage_remapped.info Lines: 100.0 % 22 22
Test Date: 2026-06-30 19:38:42 Functions: 100.0 % 23 23

           TLA  Line data    Source code
       1                 : //
       2                 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
       3                 : // Copyright (c) 2026 Michael Vandeberg
       4                 : //
       5                 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
       6                 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
       7                 : //
       8                 : // Official repository: https://github.com/cppalliance/corosio
       9                 : //
      10                 : 
      11                 : #ifndef BOOST_COROSIO_DETAIL_BUFFER_PARAM_HPP
      12                 : #define BOOST_COROSIO_DETAIL_BUFFER_PARAM_HPP
      13                 : 
      14                 : #include <boost/corosio/detail/config.hpp>
      15                 : #include <boost/capy/buffers.hpp>
      16                 : 
      17                 : #include <cstddef>
      18                 : 
      19                 : namespace boost::corosio {
      20                 : 
      21                 : /** A type-erased buffer sequence for I/O system call boundaries.
      22                 : 
      23                 :     This class enables I/O objects to accept any buffer sequence type
      24                 :     across a virtual function boundary, while preserving the caller's
      25                 :     typed buffer sequence at the call site. The implementation can
      26                 :     then unroll the type-erased sequence into platform-native
      27                 :     structures (e.g., `iovec` on POSIX, `WSABUF` on Windows) for the
      28                 :     actual system call.
      29                 : 
      30                 :     @par Purpose
      31                 : 
      32                 :     When building coroutine-based I/O abstractions, a common pattern
      33                 :     emerges: a templated awaitable captures the caller's buffer
      34                 :     sequence, and at `await_suspend` time, must pass it across a
      35                 :     virtual interface to the I/O implementation. This class solves
      36                 :     the type-erasure problem at that boundary without heap allocation.
      37                 : 
      38                 :     @par Restricted Use Case
      39                 : 
      40                 :     This is NOT a general-purpose composable abstraction. It exists
      41                 :     solely for the final step in a coroutine I/O call chain where:
      42                 : 
      43                 :     @li A templated awaitable captures the caller's buffer sequence
      44                 :     @li The awaitable's `await_suspend` passes buffers across a
      45                 :         virtual interface to an I/O object implementation
      46                 :     @li The implementation immediately unrolls the buffers into
      47                 :         platform-native structures for the system call
      48                 : 
      49                 :     @par Lifetime Model
      50                 : 
      51                 :     The safety of this class depends entirely on coroutine parameter
      52                 :     lifetime extension. When a coroutine is suspended, parameters
      53                 :     passed to the awaitable remain valid until the coroutine resumes
      54                 :     or is destroyed. This class exploits that guarantee by holding
      55                 :     only a pointer to the caller's buffer sequence.
      56                 : 
      57                 :     The referenced buffer sequence is valid ONLY while the calling
      58                 :     coroutine remains suspended at the exact suspension point where
      59                 :     `buffer_param` was created. Once the coroutine resumes,
      60                 :     returns, or is destroyed, all referenced data becomes invalid.
      61                 : 
      62                 :     @par Const Buffer Handling
      63                 : 
      64                 :     This class accepts both `ConstBufferSequence` and
      65                 :     `MutableBufferSequence` types. However, `copy_to` always produces
      66                 :     `mutable_buffer` descriptors, casting away constness for const
      67                 :     buffer sequences. This design matches platform I/O structures
      68                 :     (`iovec`, `WSABUF`) which use non-const pointers regardless of
      69                 :     the operation direction.
      70                 : 
      71                 :     @warning The caller is responsible for ensuring the type system
      72                 :     is not violated. When the original buffer sequence was const
      73                 :     (e.g., for a write operation), the implementation MUST NOT write
      74                 :     to the buffers obtained from `copy_to`. The const-cast exists
      75                 :     solely to provide a uniform interface for platform I/O calls.
      76                 : 
      77                 :     @note Do NOT `reinterpret_cast` the `mutable_buffer` array to
      78                 :     `iovec*`/`WSABUF*` and pass it to the OS, even when the layouts
      79                 :     match: no object of the target type exists in that storage, so the
      80                 :     access is undefined behavior (and `mutable_buffer` is not an
      81                 :     implicit-lifetime type, so `std::start_lifetime_as_array` cannot
      82                 :     rescue it). Copy field by field into a real platform array instead.
      83                 : 
      84                 :     @code
      85                 :     // For write operations (const buffers):
      86                 :     void submit_write(buffer_param p)
      87                 :     {
      88                 :         capy::mutable_buffer bufs[8];
      89                 :         auto n = p.copy_to(bufs, 8);
      90                 :         iovec iov[8];
      91                 :         for (std::size_t i = 0; i < n; ++i)
      92                 :         {
      93                 :             // bufs[] may reference const data - DO NOT WRITE through iov
      94                 :             iov[i].iov_base = bufs[i].data();
      95                 :             iov[i].iov_len  = bufs[i].size();
      96                 :         }
      97                 :         writev(fd, iov, n);  // read-only
      98                 :     }
      99                 : 
     100                 :     // For read operations (mutable buffers):
     101                 :     void submit_read(buffer_param p)
     102                 :     {
     103                 :         capy::mutable_buffer bufs[8];
     104                 :         auto n = p.copy_to(bufs, 8);
     105                 :         iovec iov[8];
     106                 :         for (std::size_t i = 0; i < n; ++i)
     107                 :         {
     108                 :             // bufs[] references mutable data - safe to write
     109                 :             iov[i].iov_base = bufs[i].data();
     110                 :             iov[i].iov_len  = bufs[i].size();
     111                 :         }
     112                 :         readv(fd, iov, n);  // writing
     113                 :     }
     114                 :     @endcode
     115                 : 
     116                 :     @par Correct Usage
     117                 : 
     118                 :     The implementation receiving `buffer_param` MUST:
     119                 : 
     120                 :     @li Call `copy_to` immediately upon receiving the parameter
     121                 :     @li Use the unrolled buffer descriptors for the I/O operation
     122                 :     @li Never store the `buffer_param` object itself
     123                 :     @li Never store pointers obtained from `copy_to` beyond the
     124                 :         immediate I/O operation
     125                 : 
     126                 :     @par Example: Correct Usage
     127                 : 
     128                 :     @code
     129                 :     // Templated awaitable at the call site
     130                 :     template<class Buffers>
     131                 :     struct write_awaitable
     132                 :     {
     133                 :         Buffers bufs;
     134                 :         io_stream* stream;
     135                 : 
     136                 :         bool await_ready() { return false; }
     137                 : 
     138                 :         void await_suspend(std::coroutine_handle<> h)
     139                 :         {
     140                 :             // CORRECT: Pass to virtual interface while suspended.
     141                 :             // The buffer sequence 'bufs' remains valid because
     142                 :             // coroutine parameters live until resumption.
     143                 :             stream->async_write_some_impl(bufs, h);
     144                 :         }
     145                 : 
     146                 :         io_result await_resume() { return stream->get_result(); }
     147                 :     };
     148                 : 
     149                 :     // Virtual implementation - unrolls immediately
     150                 :     void stream_impl::async_write_some_impl(
     151                 :         buffer_param p,
     152                 :         std::coroutine_handle<> h)
     153                 :     {
     154                 :         // CORRECT: Unroll immediately into platform structure.
     155                 :         // Copy field by field; do NOT reinterpret_cast the
     156                 :         // mutable_buffer array to iovec* (see the @note above).
     157                 :         capy::mutable_buffer bufs[16];
     158                 :         std::size_t n = p.copy_to(bufs, 16);
     159                 :         iovec vecs[16];
     160                 :         for (std::size_t i = 0; i < n; ++i)
     161                 :         {
     162                 :             vecs[i].iov_base = bufs[i].data();
     163                 :             vecs[i].iov_len  = bufs[i].size();
     164                 :         }
     165                 : 
     166                 :         // CORRECT: Use unrolled buffers for system call now
     167                 :         submit_to_io_uring(vecs, n, h);
     168                 : 
     169                 :         // After this function returns, 'p' must not be used again.
     170                 :         // The iovec array is safe because it contains copies of
     171                 :         // the pointer/size pairs, not references to 'p'.
     172                 :     }
     173                 :     @endcode
     174                 : 
     175                 :     @par UNSAFE USAGE: Storing buffer_param
     176                 : 
     177                 :     @warning Never store `buffer_param` for later use.
     178                 : 
     179                 :     @code
     180                 :     class broken_stream
     181                 :     {
     182                 :         buffer_param saved_param_;  // UNSAFE: member storage
     183                 : 
     184                 :         void async_write_impl(buffer_param p, ...)
     185                 :         {
     186                 :             saved_param_ = p;  // UNSAFE: storing for later
     187                 :             schedule_write_later();
     188                 :         }
     189                 : 
     190                 :         void do_write_later()
     191                 :         {
     192                 :             // UNSAFE: The calling coroutine may have resumed
     193                 :             // or been destroyed. saved_param_ now references
     194                 :             // invalid memory!
     195                 :             capy::mutable_buffer bufs[8];
     196                 :             saved_param_.copy_to(bufs, 8);  // UNDEFINED BEHAVIOR
     197                 :         }
     198                 :     };
     199                 :     @endcode
     200                 : 
     201                 :     @par UNSAFE USAGE: Storing Unrolled Pointers
     202                 : 
     203                 :     @warning The pointers obtained from `copy_to` point into the
     204                 :     caller's buffer sequence. They become invalid when the caller
     205                 :     resumes.
     206                 : 
     207                 :     @code
     208                 :     class broken_stream
     209                 :     {
     210                 :         capy::mutable_buffer saved_bufs_[8];  // UNSAFE
     211                 :         std::size_t saved_count_;
     212                 : 
     213                 :         void async_write_impl(buffer_param p, ...)
     214                 :         {
     215                 :             // This copies pointer/size pairs into saved_bufs_
     216                 :             saved_count_ = p.copy_to(saved_bufs_, 8);
     217                 : 
     218                 :             // UNSAFE: scheduling for later while storing the
     219                 :             // buffer descriptors. The pointers in saved_bufs_
     220                 :             // will dangle when the caller resumes!
     221                 :             schedule_for_later();
     222                 :         }
     223                 : 
     224                 :         void later()
     225                 :         {
     226                 :             // UNSAFE: saved_bufs_ contains dangling pointers
     227                 :             for(std::size_t i = 0; i < saved_count_; ++i)
     228                 :                 write(fd_, saved_bufs_[i].data(), ...);  // UB
     229                 :         }
     230                 :     };
     231                 :     @endcode
     232                 : 
     233                 :     @par UNSAFE USAGE: Using Outside a Coroutine
     234                 : 
     235                 :     @warning This class relies on coroutine lifetime semantics.
     236                 :     Using it with callbacks or non-coroutine async patterns is
     237                 :     undefined behavior.
     238                 : 
     239                 :     @code
     240                 :     // UNSAFE: No coroutine lifetime guarantee
     241                 :     void bad_callback_pattern(std::vector<char>& data)
     242                 :     {
     243                 :         capy::mutable_buffer buf(data.data(), data.size());
     244                 : 
     245                 :         // UNSAFE: In a callback model, 'buf' may go out of scope
     246                 :         // before the callback fires. There is no coroutine
     247                 :         // suspension to extend the lifetime.
     248                 :         stream.async_write(buf, [](error_code ec) {
     249                 :             // 'buf' is already destroyed!
     250                 :         });
     251                 :     }
     252                 :     @endcode
     253                 : 
     254                 :     @par UNSAFE USAGE: Passing to Another Coroutine
     255                 : 
     256                 :     @warning Do not pass `buffer_param` to a different coroutine
     257                 :     or spawn a new coroutine that captures it.
     258                 : 
     259                 :     @code
     260                 :     void broken_impl(buffer_param p, std::coroutine_handle<> h)
     261                 :     {
     262                 :         // UNSAFE: Spawning a new coroutine that captures 'p'.
     263                 :         // The original coroutine may resume before this new
     264                 :         // coroutine uses 'p'.
     265                 :         co_spawn([p]() -> task<void> {
     266                 :             capy::mutable_buffer bufs[8];
     267                 :             p.copy_to(bufs, 8);  // UNSAFE: original caller may
     268                 :                                  // have resumed already!
     269                 :             co_return;
     270                 :         });
     271                 :     }
     272                 :     @endcode
     273                 : 
     274                 :     @par UNSAFE USAGE: Multiple Virtual Hops
     275                 : 
     276                 :     @warning Minimize indirection. Each virtual call that passes
     277                 :     `buffer_param` without immediately unrolling it increases
     278                 :     the risk of misuse.
     279                 : 
     280                 :     @code
     281                 :     // Risky: multiple hops before unrolling
     282                 :     void layer1(buffer_param p) {
     283                 :         layer2(p);  // Still haven't unrolled...
     284                 :     }
     285                 :     void layer2(buffer_param p) {
     286                 :         layer3(p);  // Still haven't unrolled...
     287                 :     }
     288                 :     void layer3(buffer_param p) {
     289                 :         // Finally unrolling, but the chain is fragile.
     290                 :         // Any intermediate layer storing 'p' breaks everything.
     291                 :     }
     292                 :     @endcode
     293                 : 
     294                 :     @par UNSAFE USAGE: Fire-and-Forget Operations
     295                 : 
     296                 :     @warning Do not use with detached or fire-and-forget async
     297                 :     operations where there is no guarantee the caller remains
     298                 :     suspended.
     299                 : 
     300                 :     @code
     301                 :     task<void> caller()
     302                 :     {
     303                 :         char buf[1024];
     304                 :         // UNSAFE: If async_write is fire-and-forget (doesn't
     305                 :         // actually suspend the caller), 'buf' may be destroyed
     306                 :         // before the I/O completes.
     307                 :         stream.async_write_detached(capy::mutable_buffer(buf, 1024));
     308                 :         // Returns immediately - 'buf' goes out of scope!
     309                 :     }
     310                 :     @endcode
     311                 : 
     312                 :     @par Passing Convention
     313                 : 
     314                 :     Pass by value. The class contains only two pointers (16 bytes
     315                 :     on 64-bit systems), making copies trivial and clearly
     316                 :     communicating the lightweight, transient nature of this type.
     317                 : 
     318                 :     @code
     319                 :     // Preferred: pass by value
     320                 :     void process(buffer_param buffers);
     321                 : 
     322                 :     // Also acceptable: pass by const reference
     323                 :     void process(buffer_param const& buffers);
     324                 :     @endcode
     325                 : 
     326                 :     @see capy::ConstBufferSequence, capy::MutableBufferSequence
     327                 : */
     328                 : class buffer_param
     329                 : {
     330                 : public:
     331                 :     /** Construct from a const buffer sequence.
     332                 : 
     333                 :         @param bs The buffer sequence to adapt.
     334                 :     */
     335                 :     template<capy::ConstBufferSequence BS>
     336 HIT      413367 :     buffer_param(BS const& bs) noexcept : bs_(&bs)
     337          413367 :                                         , fn_(&copy_impl<BS>)
     338                 :     {
     339          413367 :     }
     340                 : 
     341                 :     /** Fill an array with buffers from the sequence.
     342                 : 
     343                 :         Copies buffer descriptors from the sequence into the
     344                 :         destination array, skipping any zero-size buffers.
     345                 :         This ensures the output contains only buffers with
     346                 :         actual data, suitable for direct use with system calls.
     347                 : 
     348                 :         @param dest Pointer to array of mutable buffer descriptors.
     349                 :         @param n Maximum number of buffers to copy.
     350                 : 
     351                 :         @return The number of non-zero buffers copied.
     352                 :     */
     353                 :     std::size_t
     354          413367 :     copy_to(capy::mutable_buffer* dest, std::size_t n) const noexcept
     355                 :     {
     356          413367 :         return fn_(bs_, dest, n);
     357                 :     }
     358                 : 
     359                 : private:
     360                 :     template<capy::ConstBufferSequence BS>
     361                 :     static std::size_t
     362          413367 :     copy_impl(void const* p, capy::mutable_buffer* dest, std::size_t n)
     363                 :     {
     364          413367 :         auto const& bs    = *static_cast<BS const*>(p);
     365          413367 :         auto it           = capy::begin(bs);
     366          413367 :         auto const end_it = capy::end(bs);
     367                 : 
     368          413367 :         std::size_t i = 0;
     369                 :         if constexpr (capy::MutableBufferSequence<BS>)
     370                 :         {
     371          414226 :             for (; it != end_it && i < n; ++it)
     372                 :             {
     373          207118 :                 capy::mutable_buffer buf(*it);
     374          207118 :                 if (buf.size() == 0)
     375              17 :                     continue;
     376          207101 :                 dest[i++] = buf;
     377                 :             }
     378                 :         }
     379                 :         else
     380                 :         {
     381          412540 :             for (; it != end_it && i < n; ++it)
     382                 :             {
     383          206281 :                 capy::const_buffer buf(*it);
     384          206281 :                 if (buf.size() == 0)
     385              24 :                     continue;
     386          412514 :                 dest[i++] = capy::mutable_buffer(
     387          206257 :                     const_cast<char*>(static_cast<char const*>(buf.data())),
     388                 :                     buf.size());
     389                 :             }
     390                 :         }
     391          413367 :         return i;
     392                 :     }
     393                 : 
     394                 :     using fn_t =
     395                 :         std::size_t (*)(void const*, capy::mutable_buffer*, std::size_t);
     396                 : 
     397                 :     void const* bs_;
     398                 :     fn_t fn_;
     399                 : };
     400                 : 
     401                 : } // namespace boost::corosio
     402                 : 
     403                 : #endif
        

Generated by: LCOV version 2.3