libs/corosio/include/boost/corosio/io_buffer_param.hpp

100.0% Lines (23/23) 100.0% Functions (11/11) -% Branches (0/0)
libs/corosio/include/boost/corosio/io_buffer_param.hpp
Line Hits 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
384