I/O runtimes: Asio and Corosio
A build of the library is on one of two I/O runtimes, chosen when it is configured:
ASIO, on Asio: every operation is an asynchronous operation of Asio, and takes any completion token. It is also there as a coroutine.
COROSIO, on Corosio: coroutines only.
Each brings the sockets, timers and TLS, on top of an async runtime: the executors, coroutines and cancellation. Asio is both in one. Corosio is the I/O only, on top of Capy, which is the async runtime.
Both build the same library and protocol sources. Only a thin runtime layer differs,
anyhttp/runtime.hpp and anyhttp/net.hpp with what they include from asio/ or corosio/.
docs/corosio-port-plan.md has how that came about.
That layer is the inline namespace anyhttp::asio or anyhttp::corosio. Its names are
anyhttp’s, Task or errors::eof, and a library of one runtime does not link
with code compiled for the other. Because asio and corosio are names in anyhttp, code
that says using namespace anyhttp writes Boost’s namespaces in full, as in
boost::asio::buffer(): a namespace alias asio of its own would be ambiguous.
What the names stand for
ASIO |
COROSIO |
|
|---|---|---|
CMake |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Operations |
|
|
TLS |
AWS-LC (default) or OpenSSL 3.5+ |
OpenSSL 3.5+ |
Programs |
|
|
Buffers are Asio’s on both, headers are Beast’s Fields, URLs Boost.URL’s.
The reference shows what the headers have for ASIO, as Doxygen is told to read them that way. So the
reference has the operations with completion tokens that a COROSIO build does not have, and the types
as they are in the table. corosio/runtime.hpp has the same names as asio/runtime.hpp, in the
same order.
The shape of an operation
On ASIO, an operation comes in two shapes. The asynchronous operation of Asio is named async_...
and takes a completion token last, which decides what it returns, and the coroutine of the plain name
wraps it. On COROSIO, the coroutine is all there is. Taking Reader::read_some() for an
example, with the types of the library:
// ASIO: the operation, which completes a handler of the signature void(error_code, size_t) ...
template <typename CompletionToken = DefaultCompletionToken>
auto async_read_some(boost::asio::mutable_buffer buffer, CompletionToken&& token = CompletionToken());
// ... and the coroutine around it.
IoTask<size_t> read_some(boost::asio::mutable_buffer buffer);
// COROSIO: the coroutine.
IoTask<size_t> read_some(boost::asio::mutable_buffer buffer);
The coroutine has the same declaration on both, so code that uses only coroutines builds with
either, which is what the guide does. It yields results, a tuple with the error first,
and reports what goes wrong instead of throwing it; IoTask<size_t> is short
for Task<results<size_t>>. Every operation has it: read_some, write, write_eof,
submit, get_response, connect, get, and the others. What it does not have is a
completion token, so an ASIO build that wants one, for a callback, for cancel_after or to have
errors thrown, uses the async_... shape, see Completion tokens in the guide.
With the types spelled out, the same piece of code on ASIO, using both shapes:
boost::asio::awaitable<std::size_t> read_twice(Reader& reader, boost::asio::mutable_buffer buffer)
{
// The asynchronous operation, with a completion token that reports errors instead of throwing.
auto [ec, n] = co_await reader.async_read_some(buffer, boost::asio::as_tuple);
if (ec)
co_return n;
// The coroutine around it.
boost::asio::awaitable<std::tuple<boost::system::error_code, std::size_t>> next =
reader.read_some(buffer + n);
auto [next_ec, m] = co_await std::move(next);
co_return n + m;
}
And on COROSIO:
boost::capy::task<std::size_t> read_twice(Reader& reader, boost::asio::mutable_buffer buffer)
{
// The coroutine, which is all there is.
auto [ec, n] = co_await reader.read_some(buffer);
if (ec)
co_return n;
boost::capy::task<std::tuple<std::error_code, std::size_t>> next = reader.read_some(buffer + n);
auto [next_ec, m] = co_await std::move(next);
co_return n + m;
}
On ASIO, the implementation of each operation is hidden behind any_completion_handler,
so that it can be compiled separately. On COROSIO, they are coroutines of Capy, Corosio’s base, cancelled through the awaiting chain’s
std::stop_token. Reader and Writer model boost::capy::ReadStream and
boost::capy::WriteStream.
What the errors are
Where the two runtimes spell an error differently, the namespace anyhttp::errors names it:
ASIO |
COROSIO |
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
the same |
Anything else compares against errc:: (boost::system::errc or std::errc), with one
exception: a host that does not resolve is netdb_errors::host_not_found on ASIO and
errc::no_such_device_or_address on COROSIO.