Hi All,
This is my review of the Capy and Corosio libraries. In order to retain
a structured response I have used the review manager’s questions as
headings and completed the review accordingly.
Disclaimer: My company has been doing a joint R&D project with the C++
Alliance which was driven by our desire to explore the viability of
migration to a coroutine-first execution model.
Context and Background: I have been using Capy/Corosio, tracking the
develop branch, in a large, multi-repository project on and off for the
past 4 months. This is a very mature codebase that has years of
production use as tier-1 market infrastructure, which relies on
ultra-low latency and resilience. It is a message passing acrhitecture
that relies (as you might expect) heavily on networking. In my opinion
this represents a great test-bed in which to try out Capy and Corosio in
a real-world setting. To place this codebase into more context it
comprises about 40 repositories all of which are impacted by this work.
Like Capy/Coroiso we adopt a layered design (a la Lakos and Large Scale
C++ - if you haven’t read his original book on this you really should)
which helps makes this kind of refactoring achievable.
This review is not a detailed dissection of Capy/Corosio, but rather
more of an experience report. The reason for that is the (relatively)
long term use of the libraries means that any specifics that caused
trips or missteps, or that needed adjusted, fixed or explained, has
happened in the past. What I am offering in the review then is my candid
opinion based on my experience to-date in the context of delivering a
business critical platform.
1. What is your evaluation of the usefulness of the libraries?
---------------------------------------------------------------
Both libraries are useful and although Capy could in theory be a subset
of Corosio it makes sense to separate them out so that other libraries
that build on the capabilities exposed by Capy can be developed.
A quick review of the documentation reveals a number of such libraries,
planned or otherwise:
- Corosio — portable coroutine networking (this review)
- Http — sans-I/O HTTP/1.1 clients and servers
- Websocket — sans-I/O WebSocket
- Beast2 — high-level HTTP/WebSocket servers
- Burl — high-level HTTP client
Note to the authors - I’d like to see links to these libraries in the
docs - even if that directs to a “coming soon” page or repo under
development. For our needs we’d definitely be looking to utilise
WebSocket functionality that allows us to replace (or integrate with)
our existing Beast-dependent code.
Let me note here that I think the separation (Corosio builds on Capy) is
valuable because it helps people reason about the more general utility
of Capy. One issue Asio always faced was that people mistakenly assumed
that the executor model used there was “Asio-only” or somehow only
relevant to I/O and networking which of course it wasn’t. It just
happened to be part of the most widely deployed library that needed such
a model, and the idea was that once standardised it could use that as an
implementation (conceptually since you wouldn’t change something that
wasn’t broken).
By having a separate Capy library that encapsulates these core concepts
I think it is easier to rationalise about the value and utility, and
allows a lighter weight dependency for users wishing only to access
those features.
Let me come back to the question of usefulness. Of course the libraries
are useful. I’d argue they are essential, so I think that’s an easy one
to answer.
However, the more subtle question is do we need these libraries, which
at a superficial glance look a lot like some other libraries that
already exist in Boost, or at least overlap some of the functionality.
For me I’d say the answer is yes, and while the documentation puts
forward its rationale for this I’d say that my summary here is that
Boost is (among other things) about incubating and evolving patterns of
best practice within a domain or subset of the design space in that domain.
To that end I think Capy and Corosio are well timed examples of what a
coroutine-first approach to executors and related facilities like
streams and networking should look like in the context of modern C++,
and should prove to be an excellent go-to for users new to this kind
work, especially those familiar with so-called async-style approaches in
other languages.
I am aware of the conversation in other reviews with respect to Capy,
and like the fact the authors recognised the disconnect in presentation
provided by the documentation and their commitment to address that. For
me I already largely held the views expressed and this is not a mental
hurdle I have struggled with, but I agree that the documentation should
be more explicit on these points.
One last point on the usefulness of the two libraries. We’ve had
coroutines for a while in the language but no compelling libraries that
(attempt to) showcase their value. In my view that’s a problem. Why?
Well it means we’ve had a number of years pass without any real
constrained feedback on the feature itself. By creating libraries that
help establish a vocabulary, and patterns of use, for coroutines we can
both better evaluate coroutines in the language within a well defined
context that provides broad value to the C++ community, while also
helping iteratively explore the libraries, and resultant vocabulary,
themselves.
This is what Boost is for, so at a fundamental level (and assuming good
enough) I welcome the proposal to bring these libraries forward for
addition. Let’s now see if the rest of my review supports that.
2. What is your evaluation of the design?
------------------------------------------
The design does a good job of retaining, as far as possible, the
concepts and ideas developed and established by Asio over the years.
From my experience of using the libraries this makes it both familiar
and accessible. Refreshingly the authors haven’t fallen into the trap of
trying to create something completely new by discarding decades of
hard-earned experience from developing high quality systems in Asio, and
seem to have reflected much of the inherent wisdom captured in the
evolution of that design.
Personally I care about this as I spent many, many hours in
conversations regarding the use and evolution of Asio as part of the
wider efforts to bring that library into the C++ standard. Aside from
that I have been using Asio for almost two decades as the core execution
model and networking facility in market infrastructure, with some of
that infrastructure underpinning the global economy throughout the most
turbulent times in modern financial markets.
My point? In short, I am acutely aware of the many tweaks and
modifications applied to Asio over the years to evolve its design and
capabilities to address real world problems, problems that genuinely
matter, and where the cost of following stubborn or hypothetical
opinions are both unacceptable and have significant ramifications.
This matters because I’d strongly argue that “what Asio does” is an
important benchmark. I’m not talking about some idiosyncratic choice of
language design like using inheritance where aggregation would do, or
members versus free functions. Backwards compatibility and an evolving
language mean we absolutely can, and should, do some things differently
in newer libraries, and from what I can see that appears to be the case
in the design of Capy and Corosio. Personally I see this approach as a
strength and it is probably the main reason I considered looking at the
libraries as candidates for use in our codebase. If not for the clear
tie-in to the Asio models I would not have touched this.
Focusing now on the libraries in question, I can say that I appreciate
the effort that has gone into the libraries to provide a first-class
experience with coroutines in C++, and while that might not be
everyone’s ideal (coroutines to underpin networking etc) it is clearly
and unashamedly the key goal of these libraries. If you need or want a
broader set of facilities then Asio is already there, but if you want to
be coroutine-first then this is what you want. Importantly, to reiterate
my earlier point, this also give us as users a choice, and as a
community, a valuable vehicle in which to more concretely explore the
coroutine support we have in the language. I think in the long term we
will all benefit from that.
On the more general topic of design I have not observed any gaps in what
we require in our codebase, or significant red flags. There have been
some small gaps over the 4 month period but those have been closed in a
timely manner and are the kind of things you’d expect from a library
under development and refinement. I can say that for the time period
that I have been tracking the libraries the design has continued to
evolve towards the libraries stated goals.
3. What is your evaluation of the implementation?
--------------------------------------------------
I’ll answer this from two perspectives:
1. What would I care about if I was forced to find an issue in the code,
or needed to read the source to really understand what’s going on? and,
2. Does the implementation appear to be of high quality?
For 1: I haven’t spent much time looking at the implementation beyond
integrating the libraries (as standalone libraries) into our build and
packaging system. I have also spent some time looking at updating and
building the documentation but otherwise not much time in the code itself.
However, for this review I took the time to browse the code and reason
about what I was reading. From a cursory review the code is consistently
structured, and mostly consistently formatted, and appears to follow
good practices if perhaps a little terse for my liking.
That said, I could see myself being able to navigate the code easily
enough in an effort to better understand, or find, or fix, an issue. I
see from other reviews that some have indeed done this, and been able to
reason sufficiently about the code to identify potential issues and make
recommendations. I think that is worth calling out. Often library
writers feel they are the only consumers of the code and so take that as
a carte-blanche to write some cryptic and unintelligible code (yes I
know in some cases there are good reasons but I am generalising here). I
definitely don’t see an issue with that here.
Regarding 2: I’ll approach this from two angles. First I’ve built and
executed tests using the libraries on numerous occasions. As part of a
large migration over to a coroutine-first approach I’ve refactored some
core libraries within our own codebase. These are foundational libraries
that touch almost all of our codebase. This means almost every
repository’s tests exercise this code and so indirectly exercise Capy
and Corosio.
The primary focus of this testing was to understand and account for any
behavioural differences, and then ensure that correctness was maintained
by achieving passing tests. The outcome of this effort was that all
tests across all components and repositories were executed and passed.
This offered a strong indicator that the underlying implementation was
of good quality and fit for purpose.
Second, I took our existing benchmark tests, which were designed to test
“representative” production scenarios in a repeatable manner (as opposed
to micro-benchmarks seeking to expose specific bottlenecks), and had
them re-run using Capy/Corosio as the underlying implementation. Of
course that meant our entire stack had to be converted from an Asio
callback model to a coroutine-first Capy/Corosio model so there was
plenty of scope to introduce issues, so this relied heavily on the
ability to test against both our original codebase, and the updated one
to confirm correctness as mentioned above.
To add some context these benchmarks are essentially run against a whole
architecture pipeline which includes business logic and so on. As I
said, “representative” of production flows. We tested 8 scenarios across
both code paths (Asio and Capy/Corosio) using different message rates
and configurations resulting in a significant set of results that
represented a cross-section of use cases within our domain.
The encouraging outcome was that all the scenarios executed correctly
and to completion, but more importantly they executed to a performance
level that in general equalled, or surpassed, our base implementation.
For me this represents quantitative evidence that the internal quality
of the libraries is of a high standard and some effort has gone into
ensuring a very high baseline for performance.
I think our success here speaks volumes for the quality of the libraries
in the context of real-world use cases.
Aside: I know the authors of the libraries are very excited about the
fact the libraries are not header-only, but really for me I see no major
benefit in that. For us it just made it harder to integrate. It's 2026
and if you are worried about compile times (still) then your code and
development approach is probably your issue. We've 40+ repos, all
header-only, with interdependencies and we don't get hung up on this. I
know for some this might be a feature, but it wasn't for us.
4. What is your evaluation of the documentation?
-------------------------------------------------
I’m going to approach this question from two perspectives: (a) structure
and style, and (b) accuracy and completeness.
In consideration of structure and style I can say that in general I
think the documentation is of very high quality and a “breath of fresh
air”: no major assumptions assumed, code examples fully formed, and
graduated and well thought out delivery of topics and features. I can
see a great deal of effort and thought has gone into the documentation
and there are too many good qualities to list them here.
I have a few nits though:
1. Capy: On the one hand the documentation for the examples is great.
(a) the documentation actually exists, and (b) it walks through the
whole of the examples. On the other hand I find it really irritating
when I see “Exercises” at the end of an example. That’s lazy. If the
exercise is worth doing then it is worth documenting. Moreover it is
undoubtedly going to tell the reader something valuable so at the very
least I’d expect some pointers to where you can find examples elsewhere
in the docs that should provide enough pointers. I like that there was
thought put into identifying canonical examples but let’s maximise their
value if we can.
2. Both: The Reference needs much better cross-linking. I also feel like
the Reference should be decomposed to display subheadings in the
navigation bar. For example Namespaces, Types, Type Aliases, Enums,
Functions, Concepts - even if that just jumps to a section heading in
the Reference page. When I’m doing a lot of dev I tend to spend a lot of
time in the Reference and having to keep drilling down from a top entry
to find what I am looking for and seeing types with no cross-links makes
it hard to use. Ideally you’d almost have a separate Reference
navigation tree/page but I can’t think how to make that work well.
3. Both: If you make a strong statement in the Introduction please make
sure you reference and link to where in the documentation you actually
explain that and why it matters. For example, in the Corosio
introduction it says, "...that provides asynchronous networking
primitives with automatic executor affinity propagation". Interesting
for sure but given this is the opening sentence about why I should care
I'd hoped to be able to read more about this and why I should care and
what that means for me, the user. I see this as a missed opportunity.
In consideration of accuracy and completeness I can say it feels an
awful lot like the Boost review arrived a bit quicker than the library
authors anticipated and the documentation is not quite up-to-date. From
the conversations in other reviews I can see that there are some
accuracy issues that are known and will be fixed so I do not have any
concerns here.
That said (and in reference to Capy in particular) I did find myself
thinking a lot about lifetime and ownership. I couldn’t find an easy way
to search the documentation (except by cloning the repo and physically
searching the built local html pages) but it struck me that ideally
there would be a section, probably under “Coroutines in Capy” called
“Lifetime and Ownership”. Almost all bugs are going to come from either
Lifetime and Ownership issues or synchronisation issues.
As someone who has spent years using callbacks in Asio where that’s (at
least for me) really clear I wondered why I couldn’t just read a section
that represented a synopsis of all the things I’d need to think about.
In the end I created one (with some LLM help to scrape the rest of the
docs) and then rebuilt the documentation to do this. I actually found it
pretty helpful so I’m happy to share that with the authors in case they
see value in adding something like that.
I would note also I had to figure out how to build the documentation
since that’s not covered. I added it to the README of my local copy.
What I did with the section was cross reference to all other parts of
the documentation that had relevant or specific documentation. The point
was it helped me navigate the areas where this mattered while also
helping me out with a high level view.
In summary then. The documentation is on the whole very, very good but
without a lot more effort it could also be accurate and a little more
complete.
5. Have you used either or both libraries? What was your experience?
---------------------------------------------------------------------
Yes. I have used the libraries as part of an internal effort to see if
we could port our codebase across to Capy and Corosio from an
Asio-callback structure. The experience has been a very positive one, as
mentioned elsewhere in this review.
6. Are the libraries ready for inclusion in Boost?
---------------------------------------------------
Yes. Adding them now will ensure good evolution and adequate feedback
from a broader group of people, inside a vehicle many developers are
familiar with (Boost). I favour these making it into Boost sooner than
later, because I believe they have a very strong foundational design but
to really evolve effectively they need the kind of usage and challenge
that we have been reading about in this review period.
7. If not, what changes would you recommend before acceptance?
---------------------------------------------------------------
I believe they are ready for inclusion and any concerns I have raised
are not blockers.
8. Do the libraries fit well within the existing Boost ecosystem?
------------------------------------------------------------------
Yes - I think they fit well and I am sure over time interoperability
with other related libraries will potentially increase in presence and
value, as the libraries see wider use within the ecosystem and as users
look to find low friction migration paths.
This is something we know about as we currently have a codebase that can
work with either Boost.Asio or Capy/Corosio. from our experience we know
it can be done (migrate or have dual implementations), but as more look
to explore this I could see helpers or other interoperability features
arise. Critically though, only if driven through usage which is as it
should be, not on some theoretical need.
9. Are there API, naming, usability, extensibility, or
implementation concerns that should be addressed?
--------------------------------------------------------
When I started writing this review last week (yes I've been following
the review conversations as I mull over my own thoughts) I was happy to
say “no, none that I care strongly about”, however after seeing some
comments in other threads I do think it is worth exploring whether or
not IoAwaitable is the best name for IoAwaitable. I don’t have a strong
preference for an alternative and could live with it as it is, but I do
think some discussion could be helpful even if that just results in a
more detailed rationale for why IoAwaitable is the right choice (for now).
Let me be clear though - I would have absolutely no problem with the
name being changed (if it made sense) _after_ acceptance. On initial
acceptance the user base will be small so I think breaking changes can
be tolerated much more early in a library’s life. Not a condition of
acceptance for me.
Closing Remarks
---------------
Boost is the right place for these libraries to grow. I take the points
regarding some kind of Asio interoperability (as mentioned by another
reviewer). Not sure what that would look like but I think if I
understand the design it should be possible to add this later if the
consensus was to do so?
On the note of Asio - I really do not see Capy/Corosio as a
_replacement_ for Asio. I think this is a misfire in the representation
of the role and value of these libraries. I see see Capy/Corosio as an
_alternative_ to Asio that has a somewhat compatible (in terms of
reasoning and design) execution model. Instead I think the libraries are
an excellent showcase for the hard earned experience of Asio, repackaged
into a leaner more focused form. That, combined with the library
separation, makes these libraries potentially an easier vehicle on which
to push for a more sensible model for standardisation.
Coming back to the libraries themselves - we have used these libraries
extensively in real world settings as “end users” of the libraries. We
have previously raised minor issues with the authors, or sought
clarification on certain aspects of the design, but these have been
addressed. We continue to explore how best to maximise our performance
but we are already at “current performance” and so are satisfied with
what we have so far.
I am sure we will field more questions and concerns as we continue in
our use, but that’s the natural life of a library, and what we want is
to also benefit from the input and concerns from others, which results
in improvements we can all benefit from. This alone drives my desire to
see these libraries in Boost.
Finally, given my only personal concerns for acceptance were
documentation updates, which I see the authors are already committed to,
I don’t feel the need to do a further review of that before acceptance.
I trust it will happen, so:
+----------------------------------------------------+
| I vote for the libraries to be ACCEPTED into Boost |
+----------------------------------------------------+
Best wishes,
Jamie