boost::corosio::io_buffer_param
A type‐erased buffer sequence for I/O system call boundaries.
Description
This class enables I/O objects to accept any buffer sequence type across a virtual function boundary, while preserving the caller's typed buffer sequence at the call site. The implementation can then unroll the type‐erased sequence into platform‐native structures (e.g., iovec on POSIX, WSABUF on Windows) for the actual system call.
Purpose
When building coroutine‐based I/O abstractions, a common pattern emerges: a templated awaitable captures the caller's buffer sequence, and at await_suspend time, must pass it across a virtual interface to the I/O implementation. This class solves the type‐erasure problem at that boundary without heap allocation.
Restricted Use Case
This is NOT a general‐purpose composable abstraction. It exists solely for the final step in a coroutine I/O call chain where:
-
A templated awaitable captures the caller's buffer sequence
-
The awaitable's `await_suspend` passes buffers across a virtual interface to an I/O object implementation
-
The implementation immediately unrolls the buffers into platform‐native structures for the system call
Lifetime Model
The safety of this class depends entirely on coroutine parameter lifetime extension. When a coroutine is suspended, parameters passed to the awaitable remain valid until the coroutine resumes or is destroyed. This class exploits that guarantee by holding only a pointer to the caller's buffer sequence.
The referenced buffer sequence is valid ONLY while the calling coroutine remains suspended at the exact suspension point where io_buffer_param was created. Once the coroutine resumes, returns, or is destroyed, all referenced data becomes invalid.
Const Buffer Handling
This class accepts both ConstBufferSequence and MutableBufferSequence types. However, copy_to always produces mutable_buffer descriptors, casting away constness for const buffer sequences. This design matches platform I/O structures (iovec, WSABUF) which use non‐const pointers regardless of the operation direction.
|
The caller is responsible for ensuring the type system is not violated. When the original buffer sequence was const (e.g., for a write operation), the implementation MUST NOT write to the buffers obtained from |
// For write operations (const buffers):
void submit_write(io_buffer_param p)
{
capy::mutable_buffer bufs[8];
auto n = p.copy_to(bufs, 8);
// bufs[] may reference const data - DO NOT WRITE
writev(fd, reinterpret_cast<iovec*>(bufs), n); // OK: read-only
}
// For read operations (mutable buffers):
void submit_read(io_buffer_param p)
{
capy::mutable_buffer bufs[8];
auto n = p.copy_to(bufs, 8);
// bufs[] references mutable data - safe to write
readv(fd, reinterpret_cast<iovec*>(bufs), n); // OK: writing
}
Correct Usage
The implementation receiving io_buffer_param MUST:
-
Call `copy_to` immediately upon receiving the parameter
-
Use the unrolled buffer descriptors for the I/O operation
-
Never store the `io_buffer_param` object itself
-
Never store pointers obtained from `copy_to` beyond the immediate I/O operation
Example: Correct Usage
// Templated awaitable at the call site
template<class Buffers>
struct write_awaitable
{
Buffers bufs;
io_stream* stream;
bool await_ready() { return false; }
void await_suspend(std::coroutine_handle<> h)
{
// CORRECT: Pass to virtual interface while suspended.
// The buffer sequence 'bufs' remains valid because
// coroutine parameters live until resumption.
stream->async_write_some_impl(bufs, h);
}
io_result await_resume() { return stream->get_result(); }
};
// Virtual implementation - unrolls immediately
void stream_impl::async_write_some_impl(
io_buffer_param p,
std::coroutine_handle<> h)
{
// CORRECT: Unroll immediately into platform structure
iovec vecs[16];
std::size_t n = p.copy_to(
reinterpret_cast<capy::mutable_buffer*>(vecs), 16);
// CORRECT: Use unrolled buffers for system call now
submit_to_io_uring(vecs, n, h);
// After this function returns, 'p' must not be used again.
// The iovec array is safe because it contains copies of
// the pointer/size pairs, not references to 'p'.
}
UNSAFE USAGE: Storing io_buffer_param
|
Never store |
class broken_stream
{
io_buffer_param saved_param_; // UNSAFE: member storage
void async_write_impl(io_buffer_param p, ...)
{
saved_param_ = p; // UNSAFE: storing for later
schedule_write_later();
}
void do_write_later()
{
// UNSAFE: The calling coroutine may have resumed
// or been destroyed. saved_param_ now references
// invalid memory!
capy::mutable_buffer bufs[8];
saved_param_.copy_to(bufs, 8); // UNDEFINED BEHAVIOR
}
};
UNSAFE USAGE: Storing Unrolled Pointers
|
The pointers obtained from |
class broken_stream
{
capy::mutable_buffer saved_bufs_[8]; // UNSAFE
std::size_t saved_count_;
void async_write_impl(io_buffer_param p, ...)
{
// This copies pointer/size pairs into saved_bufs_
saved_count_ = p.copy_to(saved_bufs_, 8);
// UNSAFE: scheduling for later while storing the
// buffer descriptors. The pointers in saved_bufs_
// will dangle when the caller resumes!
schedule_for_later();
}
void later()
{
// UNSAFE: saved_bufs_ contains dangling pointers
for(std::size_t i = 0; i < saved_count_; ++i)
write(fd_, saved_bufs_[i].data(), ...); // UB
}
};
UNSAFE USAGE: Using Outside a Coroutine
|
This class relies on coroutine lifetime semantics. Using it with callbacks or non‐coroutine async patterns is undefined behavior. |
// UNSAFE: No coroutine lifetime guarantee
void bad_callback_pattern(std::vector<char>& data)
{
capy::mutable_buffer buf(data.data(), data.size());
// UNSAFE: In a callback model, 'buf' may go out of scope
// before the callback fires. There is no coroutine
// suspension to extend the lifetime.
stream.async_write(buf, [](error_code ec) {
// 'buf' is already destroyed!
});
}
UNSAFE USAGE: Passing to Another Coroutine
|
Do not pass |
void broken_impl(io_buffer_param p, std::coroutine_handle<> h)
{
// UNSAFE: Spawning a new coroutine that captures 'p'.
// The original coroutine may resume before this new
// coroutine uses 'p'.
co_spawn([p]() -> task<void> {
capy::mutable_buffer bufs[8];
p.copy_to(bufs, 8); // UNSAFE: original caller may
// have resumed already!
co_return;
});
}
UNSAFE USAGE: Multiple Virtual Hops
|
Minimize indirection. Each virtual call that passes |
// Risky: multiple hops before unrolling
void layer1(io_buffer_param p) {
layer2(p); // Still haven't unrolled...
}
void layer2(io_buffer_param p) {
layer3(p); // Still haven't unrolled...
}
void layer3(io_buffer_param p) {
// Finally unrolling, but the chain is fragile.
// Any intermediate layer storing 'p' breaks everything.
}
UNSAFE USAGE: Fire‐and‐Forget Operations
|
Do not use with detached or fire‐and‐forget async operations where there is no guarantee the caller remains suspended. |
task<void> caller()
{
char buf[1024];
// UNSAFE: If async_write is fire-and-forget (doesn't
// actually suspend the caller), 'buf' may be destroyed
// before the I/O completes.
stream.async_write_detached(capy::mutable_buffer(buf, 1024));
// Returns immediately - 'buf' goes out of scope!
}
Passing Convention
Pass by value. The class contains only two pointers (16 bytes on 64‐bit systems), making copies trivial and clearly communicating the lightweight, transient nature of this type.
// Preferred: pass by value
void process(io_buffer_param buffers);
// Also acceptable: pass by const reference
void process(io_buffer_param const& buffers);
Member Functions
Name |
Description |
|
Construct from a const buffer sequence. |
Fill an array with buffers from the sequence. |
See Also
capy::ConstBufferSequence, capy::MutableBufferSequence
Created with MrDocs