Asio Integration
Capy provides seamless integration with both Boost.Asio and standalone Asio. This chapter explains how to use Asio’s I/O facilities within capy coroutines and how to spawn capy coroutines from Asio code.
Prerequisites
-
Completed The task Type
-
Completed Launching Coroutines
-
Familiarity with Asio’s async model and completion tokens
Overview
Capy’s Asio integration provides two directions of interoperability:
-
Capy to Asio: Await Asio async operations inside capy coroutines using
as_io_awaitable -
Asio to Capy: Spawn capy coroutines from Asio code using
asio_spawn
Additionally, you can:
-
Wrap Asio executors for use with capy (
wrap_asio_executor) -
Wrap capy executors for use with Asio (
asio_executor_adapter)
Choosing the Right Header
| Header | Use When |
|---|---|
|
Using Boost.Asio ( |
|
Using standalone Asio ( |
Both headers provide identical functionality, just targeting different Asio variants.
Awaiting Asio Operations in Capy Coroutines
The as_io_awaitable completion token allows you to co_await any Asio async operation directly within a capy io_task.
Basic Usage
#include <boost/capy/asio/boost.hpp>
#include <boost/asio.hpp>
namespace capy = boost::capy;
namespace asio = boost::asio;
capy::io_task<std::size_t> read_some(asio::ip::tcp::socket& socket)
{
std::array<char, 1024> buffer;
// Await the Asio operation using as_io_awaitable
auto [ec, bytes_read] = co_await socket.async_read_some(
asio::buffer(buffer),
capy::as_io_awaitable);
if (ec)
throw std::system_error(ec);
co_return bytes_read;
}
The as_io_awaitable token transforms the operation into an awaitable that:
-
Suspends the coroutine until the operation completes
-
Returns the completion arguments as a tuple
-
Propagates cancellation from the capy stop token
Completion Signatures
The result type depends on the Asio operation’s completion signature.
When the signature begins with error_code, the result is an io_result<Ts…>
which bundles the error code with any additional values and supports structured bindings.
| Asio Signature | co_await Result |
|---|---|
|
|
|
|
|
|
|
|
|
|
Use structured bindings for clean syntax:
auto [ec, bytes] = co_await socket.async_read_some(buffer, capy::as_io_awaitable);
Setting as Default Token
To avoid repeating as_io_awaitable on every call, rebind your I/O objects:
// Method 1: Type alias
using awaitable_socket = capy::as_io_awaitable_t::as_default_on_t<
asio::ip::tcp::socket>;
awaitable_socket socket(io_context);
auto [ec, n] = co_await socket.async_read_some(buffer); // No token needed
// Method 2: Runtime rebinding
auto socket = capy::as_io_awaitable_t::as_default_on(
asio::ip::tcp::socket(io_context));
Cancellation
When the capy coroutine receives a stop request (via its stop token), a terminal cancellation signal is sent to the Asio operation:
capy::io_task<void> cancellable_read(asio::ip::tcp::socket& socket)
{
std::array<char, 1024> buffer;
// If stop is requested, this operation will be cancelled
auto [ec, n] = co_await socket.async_read_some(
buffer, capy::as_io_awaitable);
if (ec == asio::error::operation_aborted)
{
// Cancelled via stop token
co_return;
}
// ...
}
Spawning Capy Coroutines from Asio
The asio_spawn function allows you to run capy coroutines from Asio code, using any Asio completion token to handle the result.
Basic Usage
#include <boost/capy/asio/boost.hpp>
#include <boost/asio.hpp>
capy::io_task<int> compute()
{
// ... async work ...
co_return 42;
}
int main()
{
asio::io_context io;
// Wrap the Asio executor for capy
auto exec = capy::wrap_asio_executor(io.get_executor());
// Spawn the coroutine, fire-and-forget
capy::asio_spawn(exec, compute())(asio::detached);
io.run();
}
Handling Results
Use any Asio completion token to receive the result:
// Callback handler
capy::asio_spawn(exec, compute())(
[](std::exception_ptr ep, int result) {
if (ep)
std::rethrow_exception(ep);
std::cout << "Result: " << result << "\n";
});
// With use_awaitable (in an Asio coroutine)
auto [ep, result] = co_await capy::asio_spawn(exec, compute()));
// With use_future
auto future = capy::asio_spawn(exec, compute(), asio::use_future);
auto [ep, result] = future.get();
Completion Signature
The completion signature depends on the coroutine’s return type
and the noexcept specification of await_resume:
| Coroutine | Completion Signature |
|---|---|
|
|
|
|
|
|
|
|
Wrapping Executors
Asio Executor to Capy
Use wrap_asio_executor to adapt any Asio executor for capy:
asio::io_context io;
auto capy_exec = capy::wrap_asio_executor(io.get_executor());
// Now use with capy's run/run_async
capy::run_async(capy_exec)(my_task());
The function automatically detects and handles:
-
Legacy Networking TS executors (with
on_work_started/on_work_finished) -
Modern Boost.Asio standard executors
-
Standalone Asio standard executors
Capy Executor to Asio
The asio_executor_adapter wraps a capy executor for use with Asio:
capy::thread_pool pool;
capy::asio_executor_adapter<> asio_exec(pool.get_executor());
// Use with Asio operations
asio::post(asio_exec, []{ std::cout << "Hello!\n"; });
The adapter supports Asio’s execution properties:
-
blocking:possibly,never, oralways -
outstanding_work:trackedoruntracked -
allocator: Custom allocator for handler allocation
// Require non-blocking execution
auto never_blocking = asio::require(asio_exec,
asio::execution::blocking.never);
// Require work tracking
auto tracked = asio::require(asio_exec,
asio::execution::outstanding_work.tracked);
Complete Example: Echo Server
This example demonstrates a complete echo server using capy coroutines with Boost.Asio:
#include <boost/capy/asio/boost.hpp>
#include <boost/capy/io_task.hpp>
#include <boost/asio.hpp>
#include <iostream>
namespace capy = boost::capy;
namespace asio = boost::asio;
using tcp = asio::ip::tcp;
// Rebind socket to use as_io_awaitable by default
using socket_type = capy::as_io_awaitable_t::as_default_on_t<tcp::socket>;
capy::task<void> handle_client(socket_type socket)
{
std::array<char, 1024> buffer;
for (;;)
{
// Read some data
auto [read_ec, bytes_read] = co_await socket.async_read_some(
asio::buffer(buffer));
if (read_ec)
{
if (read_ec != asio::error::eof)
std::cerr << "Read error: " << read_ec.message() << "\n";
co_return;
}
// Echo it back
auto [write_ec, bytes_written] = co_await asio::async_write(
socket,
asio::buffer(buffer.data(), bytes_read));
if (write_ec)
{
std::cerr << "Write error: " << write_ec.message() << "\n";
co_return;
}
}
}
capy::task<void> accept_loop(tcp::acceptor& acceptor)
{
auto exec = co_await capy::this_coro::executor;
for (;;)
{
// Accept a connection
auto [ec, socket] = co_await acceptor.async_accept(
capy::as_io_awaitable);
if (ec)
{
std::cerr << "Accept error: " << ec.message() << "\n";
continue;
}
// Spawn handler for this client (detached)
capy::asio_spawn(exec, handle_client(
socket_type(std::move(socket))))(asio::detached);
}
}
int main()
{
asio::io_context io;
tcp::acceptor acceptor(io, {tcp::v4(), 8080});
std::cout << "Listening on port 8080...\n";
auto exec = capy::wrap_asio_executor(io.get_executor());
capy::asio_spawn(exec, accept_loop(acceptor))(asio::detached);
io.run();
}
Buffer Sequence Integration
Capy provides utilities for seamless bidirectional conversion between Asio buffer sequences and capy’s native buffer types. This allows:
-
Using capy’s
MutableBufferSequenceandConstBufferSequencewith Asio I/O operations -
Using Asio buffer sequences with capy’s buffer concepts and algorithms
Buffer Types
Capy provides Asio-compatible buffer wrappers that inherit from capy’s native buffer types:
| Type | Description |
|---|---|
|
Extends |
|
Extends |
These types allow capy buffers to be used directly with Asio operations:
capy::asio_mutable_buffer buf(data, size);
// Works with Asio operations
co_await socket.async_read_some(buf, capy::as_io_awaitable);
The as_asio_buffer_sequence Function
The as_asio_buffer_sequence function provides bidirectional conversion between Asio and capy buffer sequences. The returned range satisfies both Asio’s buffer sequence requirements and capy’s MutableBufferSequence/ConstBufferSequence concepts.
Capy to Asio
Convert capy buffer sequences for use with Asio operations:
// Single buffers -> array of 1
auto seq1 = capy::as_asio_buffer_sequence(my_mutable_buffer);
auto seq2 = capy::as_asio_buffer_sequence(my_const_buffer);
// Capy buffer sequences -> wrapped with transforming range
capy::mutable_buffer_pair buffers = /* ... */;
auto seq3 = capy::as_asio_buffer_sequence(buffers);
// Use with Asio
co_await socket.async_read_some(seq3, capy::as_io_awaitable);
Asio to Capy
Convert Asio buffer sequences for use with capy’s buffer concepts:
// Asio buffer sequences -> wrapped with transforming range
std::vector<boost::asio::mutable_buffer> asio_buffers = /* ... */;
auto seq = capy::as_asio_buffer_sequence(asio_buffers);
// Now satisfies capy::MutableBufferSequence
static_assert(capy::MutableBufferSequence<decltype(seq)>);
// Use with capy buffer algorithms
std::size_t total = capy::buffer_size(seq);
Example: Scatter/Gather I/O
Using buffer sequences enables efficient scatter/gather I/O:
#include <boost/capy/asio/boost.hpp>
#include <boost/capy/buffers/buffer_pair.hpp>
capy::io_task<std::size_t> read_message(
boost::asio::ip::tcp::socket& socket)
{
// Header + body buffers
std::array<char, 4> header;
std::array<char, 256> body;
capy::mutable_buffer_pair buffers(
capy::mutable_buffer(header.data(), header.size()),
capy::mutable_buffer(body.data(), body.size()));
// Scatter read into both buffers
auto [ec, n] = co_await socket.async_read_some(
capy::as_asio_buffer_sequence(buffers),
capy::as_io_awaitable);
if (ec)
throw std::system_error(ec);
co_return n;
}
Stream Adapters
Capy provides bidirectional stream adapters for converting between Asio’s AsyncReadStream/AsyncWriteStream types and capy’s ReadStream/WriteStream concepts.
Capy Streams to Asio
The async_read_stream, async_write_stream, and async_stream classes wrap capy streams to provide Asio-style async_read_some and async_write_some operations with completion token support.
#include <boost/capy/asio/stream.hpp>
// Wrap a capy stream for Asio
capy::any_stream my_stream = ...;
auto exec = capy::wrap_asio_executor(io.get_executor());
capy::async_stream asio_compatible(std::move(my_stream), exec);
// Use with Asio-style async operations
auto [ec, n] = co_await asio_compatible.async_read_some(
buffer, capy::as_io_awaitable);
These adapters are useful when you have a capy stream but need to use it with code expecting Asio’s async model.
| Class | Description |
|---|---|
|
Wraps a capy |
|
Wraps a capy |
|
Wraps a capy |
Asio Streams to Capy
The asio_read_stream, asio_write_stream, and asio_stream classes wrap Asio streams to satisfy capy’s stream concepts, using as_io_awaitable internally.
#include <boost/capy/asio/stream.hpp>
// Wrap an Asio socket for capy
boost::asio::ip::tcp::socket socket(io);
capy::asio_stream stream(std::move(socket));
// Use with capy's stream interface
auto result = co_await stream.read_some(buffer);
auto [ec, bytes] = result;
These adapters allow Asio I/O objects to be used with capy’s stream algorithms and abstractions.
| Class | Description |
|---|---|
|
Wraps an Asio |
|
Wraps an Asio |
|
Wraps an Asio stream, provides both operations |
Example: Bridging Stream Types
#include <boost/capy/asio/boost.hpp>
#include <boost/capy/asio/stream.hpp>
// Convert Asio socket to capy stream
capy::task<void> process_connection(boost::asio::ip::tcp::socket socket)
{
// Wrap the Asio socket as a capy stream
capy::asio_stream stream(std::move(socket));
// Now use capy stream algorithms
std::array<char, 1024> buf;
capy::mutable_buffer buffer(buf.data(), buf.size());
while (true)
{
auto [ec, n] = co_await stream.read_some(buffer);
if (ec)
break;
// Echo back
co_await stream.write_some(capy::const_buffer(buf.data(), n));
}
}
Reference
| Header | Description |
|---|---|
|
Buffer types, iterator, and |
|
Complete Boost.Asio integration |
|
Complete standalone Asio integration |
|
The |
|
The |
|
The |
|
The |
|
Stream adapters for bidirectional Asio/capy conversion |
| Symbol | Description |
|---|---|
|
Completion token for awaiting Asio operations |
|
Rebind I/O objects to default to |
|
Spawn capy coroutines with Asio completion tokens |
|
Adapt Asio executors for capy |
|
Adapt capy executors for Asio |
|
Capy const buffer with Asio conversion operators |
|
Capy mutable buffer with Asio conversion operators |
|
Convert any buffer sequence for Asio compatibility |
|
Wrap capy ReadStream for Asio async operations |
|
Wrap capy WriteStream for Asio async operations |
|
Wrap capy Stream for Asio async operations |
|
Wrap Asio AsyncReadStream for capy ReadStream concept |
|
Wrap Asio AsyncWriteStream for capy WriteStream concept |
|
Wrap Asio stream for capy Stream concept |