Skip to content

Commit 44fa5c8

Browse files
committed
io_buffer_param is in Boost.Corosio
1 parent 3523d62 commit 44fa5c8

13 files changed

Lines changed: 818 additions & 27 deletions

File tree

doc/modules/ROOT/pages/guide/buffers.adoc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,13 +162,13 @@ This is used internally by `read()` and `write()` but can be used directly.
162162

163163
== buffer_param
164164

165-
The `buffer_param` class type-erases buffer sequences:
165+
The `io_buffer_param` class type-erases buffer sequences:
166166

167167
[source,cpp]
168168
----
169-
#include <boost/capy/buffers/buffer_param.hpp>
169+
#include <boost/corosio/io_buffer_param.hpp>
170170
171-
void accept_any_buffer(capy::buffer_param buffers)
171+
void accept_any_buffer(corosio::io_buffer_param buffers)
172172
{
173173
capy::mutable_buffer temp[8];
174174
std::size_t n = buffers.copy_to(temp, 8);
Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
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 {
19+
namespace 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+
`io_buffer_param` was created. Once the coroutine resumes,
60+
returns, or is destroyed, all referenced data becomes invalid.
61+
62+
@par Correct Usage
63+
64+
The implementation receiving `io_buffer_param` MUST:
65+
66+
@li Call `copy_to` immediately upon receiving the parameter
67+
@li Use the unrolled buffer descriptors for the I/O operation
68+
@li Never store the `io_buffer_param` object itself
69+
@li Never store pointers obtained from `copy_to` beyond the
70+
immediate I/O operation
71+
72+
@par Example: Correct Usage
73+
74+
@code
75+
// Templated awaitable at the call site
76+
template<class Buffers>
77+
struct write_awaitable
78+
{
79+
Buffers bufs;
80+
io_stream* stream;
81+
82+
bool await_ready() { return false; }
83+
84+
void await_suspend(std::coroutine_handle<> h)
85+
{
86+
// CORRECT: Pass to virtual interface while suspended.
87+
// The buffer sequence 'bufs' remains valid because
88+
// coroutine parameters live until resumption.
89+
stream->async_write_some_impl(bufs, h);
90+
}
91+
92+
io_result await_resume() { return stream->get_result(); }
93+
};
94+
95+
// Virtual implementation - unrolls immediately
96+
void stream_impl::async_write_some_impl(
97+
io_buffer_param p,
98+
std::coroutine_handle<> h)
99+
{
100+
// CORRECT: Unroll immediately into platform structure
101+
iovec vecs[16];
102+
std::size_t n = p.copy_to(
103+
reinterpret_cast<capy::mutable_buffer*>(vecs), 16);
104+
105+
// CORRECT: Use unrolled buffers for system call now
106+
submit_to_io_uring(vecs, n, h);
107+
108+
// After this function returns, 'p' must not be used again.
109+
// The iovec array is safe because it contains copies of
110+
// the pointer/size pairs, not references to 'p'.
111+
}
112+
@endcode
113+
114+
@par UNSAFE USAGE: Storing io_buffer_param
115+
116+
@warning Never store `io_buffer_param` for later use.
117+
118+
@code
119+
class broken_stream
120+
{
121+
io_buffer_param saved_param_; // UNSAFE: member storage
122+
123+
void async_write_impl(io_buffer_param p, ...)
124+
{
125+
saved_param_ = p; // UNSAFE: storing for later
126+
schedule_write_later();
127+
}
128+
129+
void do_write_later()
130+
{
131+
// UNSAFE: The calling coroutine may have resumed
132+
// or been destroyed. saved_param_ now references
133+
// invalid memory!
134+
capy::mutable_buffer bufs[8];
135+
saved_param_.copy_to(bufs, 8); // UNDEFINED BEHAVIOR
136+
}
137+
};
138+
@endcode
139+
140+
@par UNSAFE USAGE: Storing Unrolled Pointers
141+
142+
@warning The pointers obtained from `copy_to` point into the
143+
caller's buffer sequence. They become invalid when the caller
144+
resumes.
145+
146+
@code
147+
class broken_stream
148+
{
149+
capy::mutable_buffer saved_bufs_[8]; // UNSAFE
150+
std::size_t saved_count_;
151+
152+
void async_write_impl(io_buffer_param p, ...)
153+
{
154+
// This copies pointer/size pairs into saved_bufs_
155+
saved_count_ = p.copy_to(saved_bufs_, 8);
156+
157+
// UNSAFE: scheduling for later while storing the
158+
// buffer descriptors. The pointers in saved_bufs_
159+
// will dangle when the caller resumes!
160+
schedule_for_later();
161+
}
162+
163+
void later()
164+
{
165+
// UNSAFE: saved_bufs_ contains dangling pointers
166+
for(std::size_t i = 0; i < saved_count_; ++i)
167+
write(fd_, saved_bufs_[i].data(), ...); // UB
168+
}
169+
};
170+
@endcode
171+
172+
@par UNSAFE USAGE: Using Outside a Coroutine
173+
174+
@warning This class relies on coroutine lifetime semantics.
175+
Using it with callbacks or non-coroutine async patterns is
176+
undefined behavior.
177+
178+
@code
179+
// UNSAFE: No coroutine lifetime guarantee
180+
void bad_callback_pattern(std::vector<char>& data)
181+
{
182+
capy::mutable_buffer buf(data.data(), data.size());
183+
184+
// UNSAFE: In a callback model, 'buf' may go out of scope
185+
// before the callback fires. There is no coroutine
186+
// suspension to extend the lifetime.
187+
stream.async_write(buf, [](error_code ec) {
188+
// 'buf' is already destroyed!
189+
});
190+
}
191+
@endcode
192+
193+
@par UNSAFE USAGE: Passing to Another Coroutine
194+
195+
@warning Do not pass `io_buffer_param` to a different coroutine
196+
or spawn a new coroutine that captures it.
197+
198+
@code
199+
void broken_impl(io_buffer_param p, std::coroutine_handle<> h)
200+
{
201+
// UNSAFE: Spawning a new coroutine that captures 'p'.
202+
// The original coroutine may resume before this new
203+
// coroutine uses 'p'.
204+
co_spawn([p]() -> task<void> {
205+
capy::mutable_buffer bufs[8];
206+
p.copy_to(bufs, 8); // UNSAFE: original caller may
207+
// have resumed already!
208+
co_return;
209+
});
210+
}
211+
@endcode
212+
213+
@par UNSAFE USAGE: Multiple Virtual Hops
214+
215+
@warning Minimize indirection. Each virtual call that passes
216+
`io_buffer_param` without immediately unrolling it increases
217+
the risk of misuse.
218+
219+
@code
220+
// Risky: multiple hops before unrolling
221+
void layer1(io_buffer_param p) {
222+
layer2(p); // Still haven't unrolled...
223+
}
224+
void layer2(io_buffer_param p) {
225+
layer3(p); // Still haven't unrolled...
226+
}
227+
void layer3(io_buffer_param p) {
228+
// Finally unrolling, but the chain is fragile.
229+
// Any intermediate layer storing 'p' breaks everything.
230+
}
231+
@endcode
232+
233+
@par UNSAFE USAGE: Fire-and-Forget Operations
234+
235+
@warning Do not use with detached or fire-and-forget async
236+
operations where there is no guarantee the caller remains
237+
suspended.
238+
239+
@code
240+
task<void> caller()
241+
{
242+
char buf[1024];
243+
// UNSAFE: If async_write is fire-and-forget (doesn't
244+
// actually suspend the caller), 'buf' may be destroyed
245+
// before the I/O completes.
246+
stream.async_write_detached(capy::mutable_buffer(buf, 1024));
247+
// Returns immediately - 'buf' goes out of scope!
248+
}
249+
@endcode
250+
251+
@par Passing Convention
252+
253+
Pass by value. The class contains only two pointers (16 bytes
254+
on 64-bit systems), making copies trivial and clearly
255+
communicating the lightweight, transient nature of this type.
256+
257+
@code
258+
// Preferred: pass by value
259+
void process(io_buffer_param buffers);
260+
261+
// Also acceptable: pass by const reference
262+
void process(io_buffer_param const& buffers);
263+
@endcode
264+
265+
@see capy::ConstBufferSequence, capy::MutableBufferSequence
266+
*/
267+
class io_buffer_param
268+
{
269+
public:
270+
/** Construct from a const buffer sequence.
271+
272+
@param bs The buffer sequence to adapt.
273+
*/
274+
template<capy::ConstBufferSequence BS>
275+
io_buffer_param(BS const& bs) noexcept
276+
: bs_(&bs)
277+
, fn_(&copy_impl<BS>)
278+
{
279+
}
280+
281+
/** Fill an array with buffers from the sequence.
282+
283+
Copies buffer descriptors from the sequence into the
284+
destination array. If the total number of bytes across
285+
all copied buffers is zero, returns 0 regardless of
286+
how many buffer descriptors were copied.
287+
288+
@param dest Pointer to array of mutable buffer descriptors.
289+
@param n Maximum number of buffers to copy.
290+
291+
@return The number of buffers actually copied, or 0 if
292+
the total byte count is zero.
293+
*/
294+
std::size_t
295+
copy_to(
296+
capy::mutable_buffer* dest,
297+
std::size_t n) const noexcept
298+
{
299+
return fn_(bs_, dest, n);
300+
}
301+
302+
private:
303+
template<capy::ConstBufferSequence BS>
304+
static std::size_t
305+
copy_impl(
306+
void const* p,
307+
capy::mutable_buffer* dest,
308+
std::size_t n)
309+
{
310+
auto const& bs = *static_cast<BS const*>(p);
311+
auto it = capy::begin(bs);
312+
auto const end_it = capy::end(bs);
313+
314+
std::size_t i = 0;
315+
std::size_t bytes = 0;
316+
if constexpr (capy::MutableBufferSequence<BS>)
317+
{
318+
for(; it != end_it && i < n; ++it, ++i)
319+
{
320+
dest[i] = *it;
321+
bytes += dest[i].size();
322+
}
323+
}
324+
else
325+
{
326+
for(; it != end_it && i < n; ++it, ++i)
327+
{
328+
auto const& buf = *it;
329+
dest[i] = capy::mutable_buffer(
330+
const_cast<char*>(
331+
static_cast<char const*>(buf.data())),
332+
buf.size());
333+
bytes += buf.size();
334+
}
335+
}
336+
return bytes == 0 ? 0 : i;
337+
}
338+
339+
using fn_t = std::size_t(*)(void const*,
340+
capy::mutable_buffer*, std::size_t);
341+
342+
void const* bs_;
343+
fn_t fn_;
344+
};
345+
346+
} // namespace corosio
347+
} // namespace boost
348+
349+
#endif

include/boost/corosio/io_stream.hpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
#include <boost/corosio/detail/config.hpp>
1414
#include <boost/corosio/io_object.hpp>
1515
#include <boost/corosio/io_result.hpp>
16-
#include <boost/capy/buffers/buffer_param.hpp>
16+
#include <boost/corosio/io_buffer_param.hpp>
1717
#include <boost/capy/ex/any_executor_ref.hpp>
1818
#include <boost/system/error_code.hpp>
1919

@@ -201,15 +201,15 @@ class BOOST_COROSIO_DECL io_stream : public io_object
201201
virtual void read_some(
202202
std::coroutine_handle<>,
203203
capy::any_executor_ref,
204-
capy::buffer_param,
204+
io_buffer_param,
205205
std::stop_token,
206206
system::error_code*,
207207
std::size_t*) = 0;
208208

209209
virtual void write_some(
210210
std::coroutine_handle<>,
211211
capy::any_executor_ref,
212-
capy::buffer_param,
212+
io_buffer_param,
213213
std::stop_token,
214214
system::error_code*,
215215
std::size_t*) = 0;

0 commit comments

Comments
 (0)