Using ASIO random_access_file with Scatter-Gather I/O
ASIO's basic_random_access_file enables high-performance asynchronous file operations by accepting buffer sequences—collections of multiple buffers that are read or written in a single atomic operation at a specified file offset.
The chriskohlhoff/asio repository provides a portable, asynchronous I/O library where scatter-gather operations allow you to transfer data between non-contiguous memory regions and disk positions efficiently. This pattern minimizes system call overhead by packing multiple buffers into a single vectored I/O operation, implemented via platform-specific services like io_uring on Linux and I/O Completion Ports on Windows.
Understanding Scatter-Gather I/O in ASIO
Scatter-gather I/O refers to reading data from a single file offset into multiple memory buffers (scatter), or writing data from multiple buffers to a single file offset (gather). In ASIO, this is achieved through buffer sequences—types that satisfy the ConstBufferSequence or MutableBufferSequence concepts.
The basic_random_access_file class defined in include/asio/basic_random_access_file.hpp exposes member functions that accept these sequences, allowing the underlying operating system to transfer data directly into your scattered memory layout without requiring intermediate copies.
Buffer Sequence Requirements
Any scatter-gather operation requires a buffer sequence that meets ASIO's concept requirements. Valid types include:
std::array<asio::mutable_buffer, N>for fixed-size scatter operationsstd::vector<asio::const_buffer>for dynamic gather operations- Custom types implementing
begin()andend()returning buffer iterators
Each buffer in the sequence encapsulates a pointer and size, which the platform-specific service implementations translate into native vectored I/O structures like iovec (POSIX) or WSABUF (Windows).
Core Asynchronous Operations
ASIO provides two layers of abstraction for scatter-gather file I/O: low-level member functions that perform single-shot transfers, and high-level free functions that guarantee complete buffer filling.
Low-Level Member Functions
The basic_random_access_file class provides async_read_some_at and async_write_some_at as defined in include/asio/basic_random_access_file.hpp. These methods initiate a single asynchronous operation that translates directly to a vectored system call:
template<typename MutableBufferSequence, typename ReadToken>
auto async_read_some_at(uint64_t offset,
const MutableBufferSequence& buffers,
ReadToken&& token);
template<typename ConstBufferSequence, typename WriteToken>
auto async_write_some_at(uint64_t offset,
const ConstBufferSequence& buffers,
WriteToken&& token);
Important: These functions may transfer fewer bytes than the total size of the buffer sequence. The completion handler receives a std::size_t indicating the actual bytes transferred, which can be less than the sum of all buffer sizes if the operation encounters end-of-file or resource limits.
High-Level Free Functions
For applications requiring guaranteed complete transfers, include/asio/read_at.hpp provides asio::async_read_at and asio::async_write_at. These free functions repeatedly invoke the device's async_read_some_at or async_write_some_at until all buffers are completely filled or an error occurs:
asio::async_read_at(file, offset, buffers, handler);
asio::async_write_at(file, offset, buffers, handler);
These helpers preserve ASIO's cancellation support, accepting cancellation_type::terminal, partial, or total to control operation lifecycle.
Platform-Specific Implementations
Under the hood, ASIO dispatches scatter-gather requests to platform-specific service implementations that optimize the buffer sequence translation:
- Windows:
include/asio/detail/win_iocp_file_service.hpptranslates buffer sequences intoWSABUFstructures for I/O Completion Port operations - Linux:
include/asio/detail/io_uring_file_service.hppmaps sequences directly toio_uringvectored I/O submissions for kernel-bypass efficiency
Both services implement the async_read_some_at and async_write_some_at interfaces, ensuring consistent behavior across platforms while leveraging native performance characteristics.
Practical Code Examples
Writing Multiple Buffers Atomically
This example demonstrates gathering data from three separate buffers into a file at offset 0:
asio::io_context ctx;
asio::random_access_file file(ctx, "example.bin",
asio::random_access_file::write_only |
asio::random_access_file::create);
// Prepare three buffers.
std::array<asio::const_buffer, 3> bufs = {
asio::buffer(data1, size1),
asio::buffer(data2, size2),
asio::buffer(data3, size3)};
file.async_write_some_at(0, bufs,
[&](asio::error_code ec, std::size_t bytes_written) {
if (!ec) {
std::cout << "Wrote " << bytes_written << " bytes.\n";
} else {
std::cerr << "Write error: " << ec.message() << "\n";
}
});
ctx.run();
Reading into Scattered Buffers with Coroutines
Using C++20 coroutines, you can scatter-read into multiple buffers with clean asynchronous syntax:
asio::awaitable<void> read_three_buffers() {
asio::random_access_file file(co_await asio::this_coro::executor,
"example.bin",
asio::random_access_file::read_only);
std::array<char, 64> b1, b2, b3;
std::array<asio::mutable_buffer, 3> bufs = {
asio::buffer(b1),
asio::buffer(b2),
asio::buffer(b3)};
std::size_t n = co_await file.async_read_some_at(0, bufs,
asio::use_awaitable);
std::cout << "Read " << n << " bytes across three buffers.\n";
}
Ensuring Complete Transfers
When your application logic requires all buffers to be filled completely, use the high-level helper instead of the low-level member function:
asio::async_read_at(file, 0, bufs,
[&](asio::error_code ec, std::size_t total) {
if (!ec) {
std::cout << "All " << total << " bytes read.\n";
}
});
Key Considerations for Production Use
When implementing scatter-gather I/O with random_access_file, consider these critical aspects:
- Cancellation: All scatter-gather operations support per-operation cancellation. Use
asio::cancel_afteror explicit cancellation signals to abort long-running transfers - Partial Operations: Always check the bytes_transferred parameter in your completion handler when using
async_read_some_at, as short reads are normal for these low-level operations - Thread-Safety:
random_access_fileobjects are not copyable; use move semantics to transfer ownership between threads. The underlying service implementations ensure thread-safe completion handler dispatch - Offset Management: All operations accept explicit 64-bit offsets, enabling true random access without maintaining file position state
Summary
- Scatter-gather I/O in ASIO uses buffer sequences to transfer data between multiple memory regions and file offsets in a single operation
- Low-level functions
async_read_some_atandasync_write_some_atinbasic_random_access_fileprovide direct access to vectored system calls - High-level helpers
asio::async_read_atandasio::async_write_atensure complete buffer filling through automatic retry loops - Platform implementations in
win_iocp_file_service.hppandio_uring_file_service.hppoptimize the translation to native I/O vectors - Buffer sequences must satisfy MutableBufferSequence or ConstBufferSequence concepts, typically using
std::arrayorstd::vectorof ASIO buffers
Frequently Asked Questions
What is the difference between async_read_some_at and async_read_at?
async_read_some_at is a member function of basic_random_access_file that performs a single vectored read operation which may return fewer bytes than requested. async_read_at is a free function in read_at.hpp that automatically loops until all buffers are completely filled or an error occurs, providing higher-level convenience at the cost of additional internal bookkeeping.
What buffer types can I use with scatter-gather I/O?
ASIO accepts any type satisfying the ConstBufferSequence or MutableBufferSequence concepts. Common choices include std::array<asio::mutable_buffer, N> for fixed-size operations and std::vector<asio::const_buffer> for dynamic sizes. Custom types are supported if they provide begin() and end() methods returning valid buffer iterators.
How does ASIO handle scatter-gather on Windows vs Linux?
On Windows, win_iocp_file_service.hpp translates buffer sequences into WSABUF arrays for I/O Completion Port operations. On Linux, io_uring_file_service.hpp maps sequences directly to io_uring submission queue entries supporting vectored I/O. Both implementations perform the vectored system call atomically at the requested offset, providing equivalent semantics with platform-optimized performance.
Is random_access_file thread-safe?
The random_access_file object itself is not thread-safe for concurrent operations; you must synchronize access or use strand executors. However, the object is move-only (non-copyable), allowing you to transfer ownership between threads. The underlying service implementations in detail/win_iocp_file_service.hpp and detail/io_uring_file_service.hpp ensure that completion handlers are thread-safe when using appropriate executor configurations.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →