LCOV - code coverage report
Current view: top level - include/boost/corosio - io_buffer_param.hpp (source / functions) Coverage Total Hit
Test: coverage_remapped.info Lines: 100.0 % 22 22
Test Date: 2026-02-16 16:21:08 Functions: 100.0 % 21 21

           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/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 HIT      655557 :     io_buffer_param(BS const& bs) noexcept : bs_(&bs)
     310          655557 :                                            , fn_(&copy_impl<BS>)
     311                 :     {
     312          655557 :     }
     313                 : 
     314                 :     /** Fill an array with buffers from the sequence.
     315                 : 
     316                 :         Copies buffer descriptors from the sequence into the
     317                 :         destination array, skipping any zero-size buffers.
     318                 :         This ensures the output contains only buffers with
     319                 :         actual data, suitable for direct use with system calls.
     320                 : 
     321                 :         @param dest Pointer to array of mutable buffer descriptors.
     322                 :         @param n Maximum number of buffers to copy.
     323                 : 
     324                 :         @return The number of non-zero buffers copied.
     325                 :     */
     326                 :     std::size_t
     327          655557 :     copy_to(capy::mutable_buffer* dest, std::size_t n) const noexcept
     328                 :     {
     329          655557 :         return fn_(bs_, dest, n);
     330                 :     }
     331                 : 
     332                 : private:
     333                 :     template<capy::ConstBufferSequence BS>
     334                 :     static std::size_t
     335          655557 :     copy_impl(void const* p, capy::mutable_buffer* dest, std::size_t n)
     336                 :     {
     337          655557 :         auto const& bs = *static_cast<BS const*>(p);
     338          655557 :         auto it = capy::begin(bs);
     339          655557 :         auto const end_it = capy::end(bs);
     340                 : 
     341          655557 :         std::size_t i = 0;
     342                 :         if constexpr (capy::MutableBufferSequence<BS>)
     343                 :         {
     344          655912 :             for (; it != end_it && i < n; ++it)
     345                 :             {
     346          327957 :                 capy::mutable_buffer buf(*it);
     347          327957 :                 if (buf.size() == 0)
     348               5 :                     continue;
     349          327952 :                 dest[i++] = buf;
     350                 :             }
     351                 :         }
     352                 :         else
     353                 :         {
     354          655218 :             for (; it != end_it && i < n; ++it)
     355                 :             {
     356          327616 :                 capy::const_buffer buf(*it);
     357          327616 :                 if (buf.size() == 0)
     358              12 :                     continue;
     359          655208 :                 dest[i++] = capy::mutable_buffer(
     360          327604 :                     const_cast<char*>(static_cast<char const*>(buf.data())),
     361                 :                     buf.size());
     362                 :             }
     363                 :         }
     364          655557 :         return i;
     365                 :     }
     366                 : 
     367                 :     using fn_t =
     368                 :         std::size_t (*)(void const*, capy::mutable_buffer*, std::size_t);
     369                 : 
     370                 :     void const* bs_;
     371                 :     fn_t fn_;
     372                 : };
     373                 : 
     374                 : } // namespace boost::corosio
     375                 : 
     376                 : #endif
        

Generated by: LCOV version 2.3