[Capy review] Some questions

Ruben Perez via Boost <[email protected]> Tue, 23 Jun 2026 18:03:22 +0200
Newsgroups gmane.comp.lib.boost.devel
Message-ID <CACR-mdK8ysedh2TYujwUyTLVC6WX_V69E_OLOLLHg8YpFWg2DA@mail.gmail.com>
Hi all,

As you might know, I'm currently using Capy and Corosio in 3 projects:

* Boost.Redis [1]: I've implemented a Corosio API matching what we had for
Asio. Currently in a PR that will be merged if the libraries are accepted.
* A fork of Boost.MySQL [2]. It is functional, but unclear if I will
propose it as a new library, or add it to the existing one.
* A PostgreSQL library that I'm writing [3].

I'm in general happy. Coroutines make everything much simpler. I don't have
this "everything wants to eat me" feeling that writing Asio universal async
operations transmits. Benchmarks are also quite promising, well above what
we were able to get with Asio. Marcelo did the benchmarks, so I'll leave
him to tell the whole story here.

I've got some real questions that need answers before emitting a review.

1) What's the status of the interoperability with Asio, and what can we
expect? Asio currently has many users who won't migrate their existing code
from one day to another.

There is a pull request in Capy [4] addressing this. I'd like to know what
to expect. Especially, how seamless would the interaction with Corosio
(rather than Capy) be? Would the following work?

// Code using capy/corosio that creates I/O objects
capy::task<> my_capy_task()
{
    corosio::tcp_socket sock {co_await capy::this_coro::executor};
    co_await sock.connect(/* whatever */);
}

// Represents the main application, still using Asio.
// At some point, it needs to call out to code using capy/corosio
asio::awaitable<void> co_main()
{
    auto exec = capy::wrap_asio_executor(co_await
asio::this_coro::executor);
    co_await capy::asio_spawn(my_capy_task(), compute());
}

int main()
{
    asio::io_context ctx;
    asio::co_spawn(ctx, co_main(), asio::detached);
    ctx.run();
}

In real use cases, it'd be libraries (like Boost.Redis) that may create I/O
objects on behalf of the user.

2) What's the rationale for having tls_context::set_hostname but no
tls_stream::set_hostname? Say I have a tls_stream I want to re-use for
connecting to different hosts (e.g. HTTP redirection, or Redis Sentinel).
Different hosts should have different SNIs. But IIUC tls_context is
supposed to be set up beforehand and not modified after the stream is
created. How do I do this with Corosio? Note that I raised [5] some time
ago - this may be a good time to discuss it (I understand everyone has been
busy, don't take this as a criticism).

3) Asio has the concept of error dispositions [6]. In a nutshell, these are
a generalization of error codes, so custom error types can be used in error
code-aware functionality, like when_any. This is useful in code using
specialized types. Future candidate Boost.Http uses this in its router [8],
and I intended to use it as a way to attach error messages in my database
libraries. I discuss my use case in more detail in this issue [9]. Have you
considered the feature?

4) Does capy::cond::canceled add anything vs. just using
std::errc::operation_canceled?

5) I initially found the when_any semantics confusing. It uses the
equivalent to Asio's wait_for_one_success semantics [10]. Asio has also
wait_for_one, and it turns out it's the one I tend to need more. Docs [11]
propose a workaround - but io_task<std::error_code> doesn't look that good.
Is there a chance of adding first-class support for this? What is the
rationale behind making wait_for_one_success semantics the default?

6) Can I write types that satisfy ConstBufferSequence or DynamicBuffer
without explicitly depending on Capy? This would be useful for low-level
protocol libraries that want to take as few dependencies as possible.
Setting the concrete const_buffer and mutable_buffer types apart, the usual
way of representing "raw bytes" would be span<const unsigned
char>/span<std::byte>. For instance, std::as_bytes [12] uses std::byte. If
I got the reasoning behind ConstBufferSequence right [13], the purpose is
to avoid allocations with vectored I/O. I'd say it's reasonable to allow
std::array<std::span<const unsigned char>> to satisfy ConstBufferSequence.

Note that I have read The Span Reflex [14]. I agree with the need
of ConstBufferSequence. While I personally don't agree with the need of the
concrete const_buffer and mutable_buffer types, I understand the rationale
of avoiding std::byte. I'm not challenging these types, only asking for
better compatibility with std::span.

I'll probably get more questions as I explore the parts of the library that
I don't have implementation experience with.

Congratulations on the work, it's really great.

Regards,
Ruben.

[1] https://github.com/boostorg/redis/pull/417
[2] https://github.com/anarthal/mycosql
[3] https://github.com/anarthal/nativepg
[4] https://github.com/cppalliance/capy/pull/246
[5] https://github.com/cppalliance/corosio/issues/239
[6]
https://www.boost.org/doc/libs/latest/doc/html/boost_asio/reference/Disposition.html
[7] https://github.com/cppalliance/capy/issues/298
[8]
https://github.com/cppalliance/http/blob/1ab0b6fe1763618215d72baaca4859b9a4710064/include/boost/http/server/route_handler.hpp#L329
[9] https://github.com/cppalliance/capy/issues/298
[10]
https://www.boost.org/doc/libs/latest/doc/html/boost_asio/overview/composition/parallel_group.html
[11]
https://master.capy.cpp.al/capy/4.coroutines/4f.composition.html#_errors_do_not_win_wait_for_one_success
[12] https://en.cppreference.com/cpp/container/span/as_bytes
[13]
https://master.capy.cpp.al/capy/5.buffers/5a.overview.html#_the_concept_driven_alternative
[14] https://www.vinniefalco.com/p/the-span-reflex-when-concrete-thinking
_______________________________________________
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/B3XS75WZIKONZX5EWAAMRAYASPEIJGBO/