This is my formal review of the Capy and Corosio Libraries. As context I have been working with the Capy and Corosio libraries for a period of four months as part of a coroutine migration effort. I have been involved in implementing various components of, and applications within a tier-1 financial market system (deployed on Linux) that has been in production for many years. The platform currently uses Asio as an IO backbone, so using a library that mirrors the behaviour and performance of the Asio counterparts was important. While this review is about the merits of Capy/Corosio, it is natural that much of it may reference Asio as a comparator given that it is our reference point for capability and performance given our existing codebase is entirely based on the Asio library (largely using callbacks). Also note I come at this review from the position of a user picking up a new library as part of a refactoring and navigating that library based on the documentation and examples provided. I’m not trying to do anything too clever and have been able to fall-back on existing tests to assure behaviour (albeit updated to work with Capy/Corosio). *1. What is your evaluation of the usefulness of the libraries?* My goal, by using these libraries, was to build simple coroutine scaffolding around pre-existing concepts within the codebase. This was made possible by a familiar API and strong documentation. The introductions provided a good high-level overview that allowed me, given my limited knowledge and experience with coroutines, to get up to speed quickly. These libraries will prove very useful for building effective coroutine-based applications. I have used capy and corosio to write TCP/UDP servers and clients using each of the socket types, so the facilities provided by Corosio are exactly what I would expect from a networking library. As an aside I should note that all my previous experience has been with Asio and callback code so while some may feel that style of code is hard to reason about I find it more intuitive, mainly because of my experiences to date. This means, for example, that some coroutine specific patterns felt a little clunky or counter-intuitive at first. Not long after my first introduction to the libraries I attempted to rewrite some functions that execute a callback periodically when an asynchronous timer expires. This was when a new pattern was adopted: the periodic callback needs to be run in a loop, since the alternative, re-entering the same timer function indefinitely could result in a stack overflow. For me, initially, the callback style code felt easier to reason about. I had a similar experience when refactoring server code that performs read and write tasks. While the new pattern of performing the asynchronous waits in a loop made the flow of the server easier to understand from a high-level perspective, doing this as part of a callback-chain felt more natural to me. Why am I mentioning this as a general observation? Well because, as someone who might be more representative of a typical user coming from a callback-based Asio background they may face similar mental hurdles. However, and this is the point, despite this: the mental jump from callbacks to coroutines and then using a new library as well, I can say that the experience has been a good one. So none of this is a criticism, more just an observation that may lead to documentation improvements to better aid users like me. No doubt there will be alternative patterns and approaches I should have taken but I haven’t looked into that in detail yet because I was able to get code working that delivered the same or better performance than the older implementation by using the documentation as a very accessible guide. *2. What is your evaluation of the design?* The design of the library has been easy to use with limited background knowledge of C++20 coroutines. In terms of the facilities from Capy, having the capy-provided task type meant that coroutines could be composed and executed in parallel to make up small working projects with only few includes required. Using run_async in more complicated code took some getting used to, in comparison to my experience of Asio’s callback approach using, for example, boost::asio::read_some( Socket, Callback ) where the execution of the async operation is also the code that runs after the operation is complete and no more than that. Passing stop tokens through the coroutine stack automatically in the background makes cancellation a seamless task. Running child tasks within this stack is easy to orchestrate with the helpers e.g. capy::this_coro, for getting access to the environment where the stop token can be found. So all-in-all, as advertised, this is more or less a familiar design to Asio but with a focus on being coroutine-first. *3. What is your evaluation of the implementation?* My experience of the libraries is less of the implementation and more of the usage of the API, so I haven’t read a lot of the source code. Anecdotally from a testing and behavioural point of view clearly the implementation is fit for purpose as all of our tests (refactored to allow both Asio and Capy/Corosio variants) passed and performed comparably or better, so no concerns here. *4. What is your evaluation of the documentation?* The documentation is incredibly strong. The way they begin from first principles - networking, coroutine machinery and threading synchronisation etc., means that C++ developers can reference the documents for guidance on many related topics. A lot of canonical patterns are given. I found it was a simple task to adapt these to working code. *5. Have you used either or both libraries? What was your experience?* Yes, as noted, I have used both libraries for a period of about 4 months to-date. Prior to that I had no experience of working with coroutines in C++. As a result I very much appreciated that the documentation started from first principles of coroutines, allowing me to write my own awaiters and promises to get a handle on the machinery, before trying some of the same mock code with capy task and run_async. Creating my own awaiter involved creating a sleeping thread that then posted the continuation on wake-up. To get this working I had to look at the capy::delay_awaitable to see what was done with the handle and io_env, so maybe the documentation could be improved for others wanting to create their own. In trying to write more complex code I was tripped up by the lambda capture lifetime caused by temporary lambda tasks on multiple occasions. However the documentation has since been updated and explicitly warns users about this with sufficient detail to explain exactly what happens in this scenario, and proven alternatives. In the time I have been using Capy/Corosio the documentation has continued to evolve as misunderstandings arise and can be addressed and this has supported (for me) a positive experience of using the libraries but more importantly the result of using the libraries has been working, production quality code. *6. Are the libraries ready for inclusion in Boost?* Yes, the libraries are such that they provide all of the tools necessary to write coroutine code with networking facilities. For my use cases there is just one point to be made about the socket/endpoint interfaces which is mentioned below. In short, for Asio, we write resolver.resolve(host_name, port) and then try each of the endpoints, each of which is potentially IPv4 or IPv6, until one of them works, keeping the application code independent of the particular IP version. In some cases, it would be convenient if corosio supported a similar mechanism that would replace this style of application code: if( Endpoint.is_v4() ) { Socket_.set_option ( boost::corosio::socket_option ::join_group_v4( Endpoint.v4_address() ) ); } else { Socket_.set_option ( boost::corosio::socket_option ::join_group_v6( Endpoint.v6_address() ) ); } with Socket_.set_option( boost::corosio::socket_option::join_group( Endpoint ) ); Having said that, this is not a blocker. I haven’t looked into the test coverage on these libraries so cannot make any comments about this. *7. If not, what changes would you recommend before acceptance?* Nothing that would be considered a blocker. *8. Do the libraries fit well within the existing Boost ecosystem?* Yes, as a C++20 coroutine-first alternative to Asio. Capy in particular could serve as a basis for other IO library developers wishing to start from a foundation of coroutines if it was accepted alone (recognising that the review is to treat both libraries as a set). *9. Are there API, naming, usability, extensibility, or implementation concerns that should be addressed?* I have no major concerns about the libraries. The authors have been quick to address and fix any issues encountered with use. Our code would directly depend on native support for websockets. I have been told that this will be supported at a later date, but I cannot find any github issues or timelines backing this up. *Summary* For the avoidance of doubt I vote to accept these libraries into Boost on the basis that for my use cases they have proved fit for purpose. I have no strong views on the APIs and proposed changes voiced in other reviews as I’ve been tracking an evolving API for a while now and have not encountered any blockers.
On Monday, June 29th, 2026 at 3:47 PM, James Earnshaw via Boost <boost@lists.boost.org> wrote:
This is my formal review of the Capy and Corosio Libraries.
As context I have been working with the Capy and Corosio libraries for a period of four months as part of a coroutine migration effort. I have been involved in implementing various components of, and applications within a tier-1 financial market system (deployed on Linux) that has been in production for many years.
The platform currently uses Asio as an IO backbone, so using a library that mirrors the behaviour and performance of the Asio counterparts was important.
While this review is about the merits of Capy/Corosio, it is natural that much of it may reference Asio as a comparator given that it is our reference point for capability and performance given our existing codebase is entirely based on the Asio library (largely using callbacks).
Also note I come at this review from the position of a user picking up a new library as part of a refactoring and navigating that library based on the documentation and examples provided. I’m not trying to do anything too clever and have been able to fall-back on existing tests to assure behaviour (albeit updated to work with Capy/Corosio).
*1. What is your evaluation of the usefulness of the libraries?*
My goal, by using these libraries, was to build simple coroutine scaffolding around pre-existing concepts within the codebase. This was made possible by a familiar API and strong documentation. The introductions provided a good high-level overview that allowed me, given my limited knowledge and experience with coroutines, to get up to speed quickly. These libraries will prove very useful for building effective coroutine-based applications.
I have used capy and corosio to write TCP/UDP servers and clients using each of the socket types, so the facilities provided by Corosio are exactly what I would expect from a networking library.
As an aside I should note that all my previous experience has been with Asio and callback code so while some may feel that style of code is hard to reason about I find it more intuitive, mainly because of my experiences to date.
This means, for example, that some coroutine specific patterns felt a little clunky or counter-intuitive at first. Not long after my first introduction to the libraries I attempted to rewrite some functions that execute a callback periodically when an asynchronous timer expires. This was when a new pattern was adopted: the periodic callback needs to be run in a loop, since the alternative, re-entering the same timer function indefinitely could result in a stack overflow. For me, initially, the callback style code felt easier to reason about.
I had a similar experience when refactoring server code that performs read and write tasks. While the new pattern of performing the asynchronous waits in a loop made the flow of the server easier to understand from a high-level perspective, doing this as part of a callback-chain felt more natural to me.
Why am I mentioning this as a general observation? Well because, as someone who might be more representative of a typical user coming from a callback-based Asio background they may face similar mental hurdles. However, and this is the point, despite this: the mental jump from callbacks to coroutines and then using a new library as well, I can say that the experience has been a good one. So none of this is a criticism, more just an observation that may lead to documentation improvements to better aid users like me.
No doubt there will be alternative patterns and approaches I should have taken but I haven’t looked into that in detail yet because I was able to get code working that delivered the same or better performance than the older implementation by using the documentation as a very accessible guide.
*2. What is your evaluation of the design?*
The design of the library has been easy to use with limited background knowledge of C++20 coroutines. In terms of the facilities from Capy, having the capy-provided task type meant that coroutines could be composed and executed in parallel to make up small working projects with only few includes required.
Using run_async in more complicated code took some getting used to, in comparison to my experience of Asio’s callback approach using, for example, boost::asio::read_some( Socket, Callback ) where the execution of the async operation is also the code that runs after the operation is complete and no more than that.
Passing stop tokens through the coroutine stack automatically in the background makes cancellation a seamless task. Running child tasks within this stack is easy to orchestrate with the helpers e.g. capy::this_coro, for getting access to the environment where the stop token can be found.
So all-in-all, as advertised, this is more or less a familiar design to Asio but with a focus on being coroutine-first.
*3. What is your evaluation of the implementation?*
My experience of the libraries is less of the implementation and more of the usage of the API, so I haven’t read a lot of the source code.
Anecdotally from a testing and behavioural point of view clearly the implementation is fit for purpose as all of our tests (refactored to allow both Asio and Capy/Corosio variants) passed and performed comparably or better, so no concerns here.
*4. What is your evaluation of the documentation?*
The documentation is incredibly strong. The way they begin from first principles - networking, coroutine machinery and threading synchronisation etc., means that C++ developers can reference the documents for guidance on many related topics. A lot of canonical patterns are given. I found it was a simple task to adapt these to working code.
*5. Have you used either or both libraries? What was your experience?*
Yes, as noted, I have used both libraries for a period of about 4 months to-date. Prior to that I had no experience of working with coroutines in C++. As a result I very much appreciated that the documentation started from first principles of coroutines, allowing me to write my own awaiters and promises to get a handle on the machinery, before trying some of the same mock code with capy task and run_async.
Creating my own awaiter involved creating a sleeping thread that then posted the continuation on wake-up. To get this working I had to look at the capy::delay_awaitable to see what was done with the handle and io_env, so maybe the documentation could be improved for others wanting to create their own.
In trying to write more complex code I was tripped up by the lambda capture lifetime caused by temporary lambda tasks on multiple occasions. However the documentation has since been updated and explicitly warns users about this with sufficient detail to explain exactly what happens in this scenario, and proven alternatives.
In the time I have been using Capy/Corosio the documentation has continued to evolve as misunderstandings arise and can be addressed and this has supported (for me) a positive experience of using the libraries but more importantly the result of using the libraries has been working, production quality code.
*6. Are the libraries ready for inclusion in Boost?*
Yes, the libraries are such that they provide all of the tools necessary to write coroutine code with networking facilities. For my use cases there is just one point to be made about the socket/endpoint interfaces which is mentioned below.
In short, for Asio, we write resolver.resolve(host_name, port) and then try each of the endpoints, each of which is potentially IPv4 or IPv6, until one of them works, keeping the application code independent of the particular IP version.
In some cases, it would be convenient if corosio supported a similar mechanism that would replace this style of application code:
if( Endpoint.is_v4() ) { Socket_.set_option ( boost::corosio::socket_option ::join_group_v4( Endpoint.v4_address() ) ); } else { Socket_.set_option ( boost::corosio::socket_option ::join_group_v6( Endpoint.v6_address() ) ); }
with
Socket_.set_option( boost::corosio::socket_option::join_group( Endpoint ) );
Having said that, this is not a blocker.
I haven’t looked into the test coverage on these libraries so cannot make any comments about this.
*7. If not, what changes would you recommend before acceptance?*
Nothing that would be considered a blocker.
*8. Do the libraries fit well within the existing Boost ecosystem?*
Yes, as a C++20 coroutine-first alternative to Asio. Capy in particular could serve as a basis for other IO library developers wishing to start from a foundation of coroutines if it was accepted alone (recognising that the review is to treat both libraries as a set).
*9. Are there API, naming, usability, extensibility, or implementation concerns that should be addressed?*
I have no major concerns about the libraries. The authors have been quick to address and fix any issues encountered with use.
Our code would directly depend on native support for websockets. I have been told that this will be supported at a later date, but I cannot find any github issues or timelines backing this up.
*Summary*
For the avoidance of doubt I vote to accept these libraries into Boost on the basis that for my use cases they have proved fit for purpose. I have no strong views on the APIs and proposed changes voiced in other reviews as I’ve been tracking an evolving API for a while now and have not encountered any blockers. _______________________________________________ Boost mailing list -- boost@lists.boost.org To unsubscribe send an email to boost-leave@lists.boost.org https://lists.boost.org/mailman3/lists/boost.lists.boost.org/ Archived at: https://lists.boost.org/archives/list/boost@lists.boost.org/message/YLT26KCU...
Thank you kindly for the detailed review James.
participants (2)
-
James Earnshaw -
Steve Gerbino