include/boost/corosio/detail/buffer_param.hpp

100.0% Lines (22/22) 100.0% List of functions (13/13)
buffer_param.hpp
f(x) Functions (13)
Function Calls Lines Blocks
boost::corosio::buffer_param::buffer_param<boost::capy::const_buffer [3]>(boost::capy::const_buffer const (&) [3]) :336 4x 100.0% 100.0% boost::corosio::buffer_param::buffer_param<boost::capy::const_buffer>(boost::capy::const_buffer const&) :336 206228x 100.0% 100.0% boost::corosio::buffer_param::buffer_param<boost::capy::detail::slice_impl<boost::capy::const_buffer>::data_view>(boost::capy::detail::slice_impl<boost::capy::const_buffer>::data_view const&) :336 14x 100.0% 100.0% boost::corosio::buffer_param::buffer_param<boost::capy::detail::slice_impl<boost::capy::mutable_buffer>::data_view>(boost::capy::detail::slice_impl<boost::capy::mutable_buffer>::data_view const&) :336 16x 100.0% 100.0% boost::corosio::buffer_param::buffer_param<boost::capy::mutable_buffer>(boost::capy::mutable_buffer const&) :336 207086x 100.0% 100.0% boost::corosio::buffer_param::buffer_param<std::array<boost::capy::const_buffer, 0ul> >(std::array<boost::capy::const_buffer, 0ul> const&) :336 1x 100.0% 100.0% boost::corosio::buffer_param::buffer_param<std::array<boost::capy::const_buffer, 2ul> >(std::array<boost::capy::const_buffer, 2ul> const&) :336 5x 100.0% 100.0% boost::corosio::buffer_param::buffer_param<std::array<boost::capy::const_buffer, 3ul> >(std::array<boost::capy::const_buffer, 3ul> const&) :336 5x 100.0% 100.0% boost::corosio::buffer_param::buffer_param<std::array<boost::capy::mutable_buffer, 2ul> >(std::array<boost::capy::mutable_buffer, 2ul> const&) :336 2x 100.0% 100.0% boost::corosio::buffer_param::buffer_param<std::array<boost::capy::mutable_buffer, 3ul> >(std::array<boost::capy::mutable_buffer, 3ul> const&) :336 4x 100.0% 100.0% boost::corosio::buffer_param::buffer_param<std::span<boost::capy::const_buffer const, 18446744073709551615ul> >(std::span<boost::capy::const_buffer const, 18446744073709551615ul> const&) :336 2x 100.0% 100.0% boost::corosio::buffer_param::copy_to(boost::capy::mutable_buffer*, unsigned long) const :354 413367x 100.0% 100.0% unsigned long boost::corosio::buffer_param::copy_impl<boost::capy::const_buffer>(void const*, boost::capy::mutable_buffer*, unsigned long) :362 2x 91.7% 93.0%
Line TLA Hits 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 413367x buffer_param(BS const& bs) noexcept : bs_(&bs)
337 413367x , fn_(&copy_impl<BS>)
338 {
339 413367x }
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 413367x copy_to(capy::mutable_buffer* dest, std::size_t n) const noexcept
355 {
356 413367x return fn_(bs_, dest, n);
357 }
358
359 private:
360 template<capy::ConstBufferSequence BS>
361 static std::size_t
362 413367x copy_impl(void const* p, capy::mutable_buffer* dest, std::size_t n)
363 {
364 413367x auto const& bs = *static_cast<BS const*>(p);
365 413367x auto it = capy::begin(bs);
366 413367x auto const end_it = capy::end(bs);
367
368 413367x std::size_t i = 0;
369 if constexpr (capy::MutableBufferSequence<BS>)
370 {
371 414226x for (; it != end_it && i < n; ++it)
372 {
373 207118x capy::mutable_buffer buf(*it);
374 207118x if (buf.size() == 0)
375 17x continue;
376 207101x dest[i++] = buf;
377 }
378 }
379 else
380 {
381 412540x for (; it != end_it && i < n; ++it)
382 {
383 206281x capy::const_buffer buf(*it);
384 206281x if (buf.size() == 0)
385 24x continue;
386 412514x dest[i++] = capy::mutable_buffer(
387 206257x const_cast<char*>(static_cast<char const*>(buf.data())),
388 buf.size());
389 }
390 }
391 413367x 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
404