Hi, this is my review of the proposed Boost.Async. Before I issue my
final decision (ACCEPT/REJECT) I would like to ask the author some
questions.
First of all, I have plenty of reasons to want a library such as
Boost.Async into Boost. Some of them are
* A common concern I hear from my peers is that Asio is too difficult
to get started with. One of the reasons for that is that its Universal
Asynchronous Model built around the completion token is too generic
for most use cases and in some cases encourages just bad practice like
the use_future token. A library like Boost.Async that adds a thin
coroutine-only layer over Asio and its corresponding facilities like
select, gather etc. will lower the entrance barrier in the Asio
asynchronous world.
* Docs: Even Asio experts agree that Asio documentation is not easily
digestible (async_initiate I am looking at you) and I think it will be
difficult for its author alone to address users' complaints given how
low-level and generic Asio is. Also, regular users should not be
bothered with many of the details. Boost.Async documentation is linear
and to the point. It uses names that are more easily recognized by
people coming from other languages, like select and with.
* NodeJs and Go Tokio, etc. have shown us that successful networking
libraries will flourish once a decent asynchronous environment is
available. We have more than that, Asio is robust and battle tested,
but I am afraid it might not be the correct ground on which high-level
abstractions should be prototyped and evolved, not every library needs
Duff's device to perform some milliseconds better. As I mentioned
above, it is perhaps too generic and low-level and a layer over it
might be necessary. Boost.Async looks like the correct step in that
direction. We will be one step closer to writing code that is as
simple as python, NodeJs, etc. but that runs at C++ speed.
* It will help us gather more field experience with C++20 coroutines
in domains where C++ is extensively used, like high-performance
network servers.
* Boost.Async has good defaults, for example a single threaded
context. Too many people play around with multi-thread io_context and
strands not knowing it might actually have a negative impact on
performance. This is also likely to play well with other network Boost
networking libraries like Boost.Beast, Boost.MySql and Boost.Redis.
Also, the automatic installation of signal handling is a good thing.
Q1: Default completion token for other Boost libraries
================================================================
I guess it will be a very common thing for all apps using Boost.Async
to have to change the default completion token to use_op or use_task.
Like you do in the examples
> using tcp_acceptor = async::use_op_t::as_default_on_t<tcp::acceptor>;
> using tcp_socket = async::use_op_t::as_default_on_t<tcp::socket>;
Could this be provided automatically, at least for other Boost
libraries? I know this would be a lot of work, but it would make user
code less verbose and users would not have to face the token concept
at first.
Q2: Lazy vs Eager
================================================================
> It’s an eager coroutine and recommended as the default;
Great, we can experiment with eagerness and laziness. But why is an
eager coroutine recommended as default?
Q3: async_ready looks great
================================================================
> We can however implement our own ops, that can also utilize the
> async_ready optimization. To leverage this coroutine feature, async
> provides an easy way to create a skipable operation:
I think I have a use case for this feature: My first implementation of
the RESP3 parser for Boost.Redis was based on Asio's async_read_until,
which like every Asio async function, calls completion as if by post.
The cost of this design is however high in situations where the next
\r\n delimiter is already in the buffer when async_read_until is
called again. The resulting rescheduling with post is actually
unnecessary and greatly impacts performance, being able to skip the
post has performance benefits. But what does *an easy way to create a
skipable operation* actually mean? Does it
- avoid a suspension point?
- avoid a post?
- act like a regular function call?
Q4: Synchronization primitives
================================================================
Will this library ever add synchronization primitives like async
mutexes, condition variables, barriers etc. Or are they supposed to be
used from an external library like proposed Boost.Sem.
Q5: Boost.Async vs Boost.Asio
================================================================
I use C++20 coroutines whenever I can but know very little about their
implementation. They just seem to work in Asio and look very flexible
with use_awaitable, deferred, use_promise and use_coro. What advantage
will Boost.Async bring over using plain Asio in regards to coroutine?
NOTE1
================================================================
> Please state your experience with C++ coroutines and ASIO in your
> review, and how many hours you spent on the review.
I have spent a bit more than a day reading the docs, writing the
review and integrating Boost.Redis.
My experience with C++20 coroutines is limited to using them with Asio.
I would also like to thank Klemens for submitting Boost.Async and
Niall for offering to be its review manager.
Marcelo