LCOV - code coverage report
Current view: top level - /jenkins/workspace/boost-root/boost/corosio - io_buffer_param.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 100.0 % 23 23
Test Date: 2026-02-04 19:19:32 Functions: 100.0 % 21 21

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

Generated by: LCOV version 2.3