Asio in amongoc#

Warning

This page is for amongoc developers and the documented components and behavior are not part of any public API guarantees.

This page will explain the basics of Asio and how it is used internally in amongoc.

Asio vs Boost.Asio#

Asio comes in two flavors: Boost, and non-Boost (i.e. “Standalone”).

Boost.Asio is an automatically-converted version of Asio that has all APIs moved from ::asio to the ::boost::asio namespace, and makes use of some Boost libraries in place of standard library components. The following differences are of note:

  1. All components in ::asio:: are in ::boost::asio::

  2. Asio accepts an asio::error_code, which is a typedef to std::error_code in standalone Asio, and a typedef of boost::system::error_code in Boost.Asio.[1]

The Asio Asynchrony Model#

Asio asynchrony is created using asynchronous APIs that accept an object known as a CompletionToken, which informs the operation how to initiate and how to complete.

Asio’s async API’s can be further broken down into two classes: I/O objects, and asynchronous algorithms.

I/O Objects#

Asio I/O objects are objects that abstract the implementaion of an underlying system resource that performs asynchronous (or synchronous) operations. The low-level Asio I/O objects must be associated with an asio::io_context.

The Asio I/O objects are only used within amongoc’s default event loop implementation.

Asynchronous Algorithms#

Asio’s asynchronous algorithms are extremely powerful APIs that do not rely on a particular event loop or any particular I/O object implementation. Instead, the algorithms are written generically in terms of asynchronous object concepts, such as asio::AsyncReadStream and asio::AsyncWriteStream. As long as the operands to the algorithms meet the generic concept requirements, the algorithms will Just Work™.

The Asio algorithms are used as the basis for the wire protocol implementation in amongoc. The amongoc::tcp_connection_rw_stream type implements asio::AsyncReadStream and asio::AsyncWriteStream, so it can be used for any Asio algorithm that expects such an interface.

APIs of Note#

Asio APIs#

class asio::io_context#

This is the default Asio event loop type. I/O objects are created and associated with an io_context.

In amongoc, io_context is used as the backing implementation of the default event loop.

At no other place in amongoc is asio::io_context used for any purpose. amongoc allows the user to provide their own event loop, so we cannot assume that we have an Asio io_context available.

template<typename T>
concept asio::CompletionToken#

This is an exposition-only concept from Asio for CompletionToken objects.

A CompletionToken is an object that tells an asynchronous operation API function two things:

  1. How to initiate an operation

  2. How to complete an operation

Completion tokens are further subdivided by the type of completion signature they expect. For example, WriteToken, ReadToken, HandshakeToken.

In the simplest case, a CompletionToken is simply a callback function that accepts arguments for information about the completed operation. When such a callback is used as a token, the operation is initiated immediately and the function is called when it completes.

CompletionTokens can also customize the initiation and return value of the initiating function in order to change the way the operation is initiated. Asio ships with several completion tokens that tweak the way a function is invoked. For example, asio::deferred is a completion token that causes the operation to return an initiator function, that will only start the operation when it is called.

amongoc defines one custom CompletionToken of interest: amongoc::asio_as_nanosender

auto asio::deferred#

A CompletionToken that defers the initiation of an associated operation. Refer to the Asio documentation.

template<typename T>
concept asio::AsyncReadStream#
CompletionToken auto token#
amongoc::wire::mutable_buffer_sequence auto bufs#
T &stream#

Requires

Must read at least one byte of data from the underlying stream into the buffers bufs. When finished, must complete with void(asio::error_code, std::size_t).

Note

When we define AsyncReadStream types in amongoc, we assume that token is a regular completion handler: It does not use the full CompletionToken interface.

template<typename T>
concept asio::AsyncWriteStream#
CompletionToken auto token#
amongoc::wire::const_buffer_sequence auto bufs#
T &stream#

Requires

Must write at least one byte of data into the underlying stream from the buffers bufs, and complete with void(asio::error_code, std::size_t).

amongoc APIs for Asio#

auto amongoc::asio_as_nanosender#

This object implements the asio::CompletionToken interface and causes any Asio asynchronous operation APIs to return a nanosender instead of initiating the operation. This allows any Asio API to be used with the amongoc nanosenders transparently. The sends_t of the resulting nanosender is based on the completion signature of the associated operation via the following table:

Asio completion signature

Resulting sends_t

void()

mlib::unit

void(asio::error_code)

result<mlib::unit, std::error_code>

void(asio::error_code, T) (for some type \(T\))

result<T, std::error_code>

Asio supports operations that have more than one completion signature, but asio_as_nanosender does not currently support this, and we do not use any Asio operations that do this.

When asio_as_nanosender is used as an asio::CompletionToken for an operation, the return value from the Asio API will be an unspecified type that implements nanosender.

template<typename T>
concept amongoc::wire::const_buffer_sequence#
template<typename T>
concept amongoc::wire::mutable_buffer_sequence#

These concepts correspond to Asio’s ConstBufferSequence and MutableBufferSequence concepts.

class amongoc::tcp_connection_rw_stream#

This is a move-only class type that partially implements asio::AsyncReadStream and asio::AsyncWriteStream.

amongoc_loop *loop#
unique_box conn#

loop is the event loop associated with the connection conn. conn is an opaque box that was obtained using amongoc_loop_vtable::tcp_connect().

void async_read_some(wire::mutable_buffer_sequence auto &&bufs, auto &&cb)#
void async_write_some(wire::const_buffer_sequence auto &&bufs, auto &&cb)#

Implement the low-level read/write operations that Asio expects in order to construct high-level I/O algorithms.

The cb parameter must be a regular completion callback. The full asio::CompletionToken interface is not implemented.

Note

Don’t use these directly. Instead, use the Asio algorithms, which will allow you to use a proper asio::CompletionToken and have stronger behavioral guarantees.

These member functions will invoke amongoc_loop_vtable::tcp_read_some() and amongoc_loop_vtable::tcp_write_some(), respectively.

Footnotes