[capy] Andrzej's feedback

Andrzej Krzemienski via Boost <[email protected]> Sun, 5 Jul 2026 10:46:53 +0200
Newsgroups gmane.comp.lib.boost.devel
Message-ID <CAOenAXheL4OJHw=xseC=Nrdxsh1pxsqM1vDiRUg6KZHYZVZVjQ@mail.gmail.com>
Hi Everyone,
I do not feel qualified in the domain of asynchronous I/O to provide a
responsible review. But let me share some observations instead.

I would like to thank the Capy/Corosio team for developing and sharing
these libraries. This is a huge scope, a huge effort, a great lot of moving
parts and details to tackle. I am not even sure how this can be reviewed
with the level of scrutiny that is required.

I appreciate the unprecedented level of the design rationale. The library
comes not only with sources, tests and library documentation, but also with
lots of documents presenting rationale for every tiny decision. The
contribution of this library is not only the code that users can use, but
also the accumulated (and shared) knowledge that can be used by anyone, for
writing their own I/O framework and avoiding the past mistakes. This alone
builds my trust in the authors and the quality of the library.

The value proposition of Capy and Corosio is not only in providing a faster
and easier to use alternative to ASIO. It is also the responsiveness of the
authors and openness for contribution. It gives me confidence that if I
encounter some problems with the library in the future, they will be solved
in a predictable way.

Now, the concerns.

A couple of things in the IoAwaitable protocol give discomfort:
1. Having to use thread_local storage
2. Having to use the double-call syntax.
3. Having to use pass they stop_token via await_ready (and therefore risk
UB when stop_token is queried before the first suspend).

Ideally, these things should not be present in a protocol. However, it
seems impossible to provide any alternative design, with coroutines as
its basic vehicle, that would have fewer problems like these.

What can we conclude from this? It looks like C++ coroutines are not well
suited for I/O-like flows with environment propagation. You can do it
*somehow*: just pass all you need in the coroutine's function parameter
list, but it is not ergonomic or type-safe. I think the review of Capy may
be exposing a design flaw in coroutines. If the goal of Capy is to provide
the best third-party library asynchronous-I/O coroutine implementation,
then the current design may indeed be the best (as in "no better design can
be thought of"). But if the goal is to aim at standardization, I do not
think that such unsafe design would work. The action would rather be, "fix
coroutines in the language".

This makes me think that coroutines in C++ may be a dead end. This would be
consistent with some other observations: scarce adoption in the community
and WG21 itself choosing to use a different model for asynchronous flows.

One could question one design choice in Capy: why go with coroutines when
you have to fight an uphill battle with the language? But then, given that
it outperforms ASIO and std::execution, it has clearly earned its place. It
surely deserves its place in Boost as "the best you can do with coroutines
for I/O". But if I were to consider switching to a different library for
async I/O I would also consider/evaluate non-coroutine alternatives.

On the usage of error_code and reporting failures.

The library doesn't give me a clear picture on how it handles and how I
should handle the non-clear-sky situations. For things that represent
obvious disappointments -- broken connection, problems on the wire, not
enough memory to process a request -- it sometimes returns an error_code
and sometimes throws an exception. It looks like the division is:
* for things happening on the wire, or in the system's native part of the
I/O implementation, use error_code
* for problems with resources in the implementation of the library throw
exceptions.

But then also, the library returns error_codes for situations that are not
errors by any measure: for situations that I desire:
* Successfully receiving full stream content.
* Stopping the running process upon my request.

The only model for this that I can adapt is that the contract of stream
interaction is similar to the interaction with a human postman:

* They can deliver you a parcel
* But they can also tell you "I lost your parcel", or "while obtaining the
parcel I noticed that the box is half-open", or "I had a car accident, and
couldn't deliver your parcel"

That is, your success is having had a meeting with the postman and chatted
with them and maybe also having obtained the parcel. In that model not
receiving a parcel is not an error.

This makes sense for the interaction with a single stream, but breaks for
the next layer:
1. when you have to interact with two streams at once: pull_from, push_to.
2. when you have "algorithms" such as when_any, when you have to nominate
the first successful task. But in the postman model nearly everything is
successful. Having read the stream status is success.

In the case of push_to/pull_from, you might be interested in knowing which
of the two streams was the cause of disappointment. But the interface does
not let you get this information. Interaction with two postmen would
require two statuses.

While the contract of `stream.read_some` is "I promise you nothing else
than to inform you what happened", it is not clear to me what the contract
of pull_from is. Is it:

a) I promise to pump the data from one stream to the other. We will call it
a contract violation if I fail.
OR
b) I will just issue some stream instructions in sequence and tell you what
the readouts of controls are. My having told you the readout is already a
success; you interpret what this means for you.

In this case I do not buy the "ASIO got that right" argument. If ASIO is
also unclear about the contract, then ASIO got that wrong also. There are a
lot of libraries that are popular, contribute to the success of servers
that employ them, but that get their contracts wrong.

I am bringing the contract aspect, because I know that the authors of Capy
care deeply about this level of clarity. A number of libraries were
accepted to Boost while having a muddy contract, and this did not seem to
be a show-stopper.

My observations are based on reading the documentation and the
implementation of some parts of Capy and Corosio, running toy examples and
discussing the design aspects with the authors.

Disclaimer: I am affiliated with the C++ Alliance, and have occasionally
contributed to Capy.

Regards,
&rzej;
_______________________________________________
Boost mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://lists.boost.org/mailman3/lists/boost.lists.boost.org/
Archived at: https://lists.boost.org/archives/list/[email protected]/message/M6NXDYD7ZUNBKBXJXNCX2BCEZWSWNBU7/