Re: Capy Review

Vinnie Falco via Boost <[email protected]> Thu, 25 Jun 2026 06:11:45 -0700
Newsgroups gmane.comp.lib.boost.devel
Message-ID <CA+EzHGfchfKz-ezkbROuxzeHefOfsKS6KcuvofKn7ThYQNf6CQ@mail.gmail.com>
On Thu, Jun 25, 2026 at 4:56 AM Rainer Deyke via Boost <
[email protected]> wrote:

> Pretty sure that's not actually true.


Capy provides an execution model for coroutines which offers one simple
invariant:

    A coroutine will always be resumed by the same Executor object which
was used to launch it.

This should probably be stated in the Capy docs up front.


> This isn't about Capy vs TooManyCooks.  This is Capy vs an entire world of
> C++ coroutine code that can interact with each other more or less
> seamlessly.


Capy's invariant, stated plainly above, is the coroutine-native expression
of the same invariant which Boost.Asio's execution model maintains. Asio's
normative requirement, from
https://www.boost.org/doc/libs/latest/doc/html/boost_asio/reference/asynchronous_operations.html
:

    The completion handler shall be submitted for execution as if by
performing dispatch(ex2, std::move(f)).

Asio got many things right. Capy takes those things and expresses them with
a coroutine-native API. Or as I like to call it: coroutine-ONLY.

Corosio similarly borrows the good parts of Asio unabashedly. And it uses
Capy for its execution model. Both libraries, built from Boost.Asio's 24
years of experience getting things right. There's a reason we asked Jeff
Garland to be the review manager, as he was also the review manager for
Boost.Asio.

"Capy vs the entire world of C++ coroutine code" is actually "Capy
continues 24 years of proven Boost design, while the rest of the coroutine
world chose the path of least resistance."


> And, to be clear, I'm reviewing Capy as a replacement for TMC, not as an
> extension.


Every misconception, misgiving, and misapprehension can be derived from a
failure to understand the Capy execution model.

One could infer that "you don't understand Capy" and I think that's both
the charitable reading and also the correct reading. The documentation
should make the invariant clear. That's on us. The documentation problem is
surfacing in the Boost review, and this is a good thing. That's what Boost
reviews are for. We've been steeping in it so long that we can't see it
from the outside, for someone encountering it cold.

Now let's review each of your objections through the lens of Capy's
execution guarantees (some quotes are paraphrased):

1. "Capy wants to be the only coroutine library." This follows from not
understanding why the boundary exists. The boundary enforces the invariant.
It's the same reason Asio dispatches handlers through the associated
executor instead of calling them directly. You called resume() manually.
You called the handler directly. We don't do that.

2. "Capy is too restrictive to be useful." This conflates "I can't bypass
the executor" with "I can't do anything." The restriction is the feature.

3. "Plain awaitables should just work." A plain awaitable can resume on any
thread via raw coroutine_handle::resume(). That breaks the invariant. The
compile error prevents it. You went around it.

4. "The two-call syntax is brittle." The two-phase syntax exists to deliver
the frame allocator to operator new before the coroutine body executes.
This is what supporting custom allocators looks like, which TMC doesn't
offer.

5. "Not enough synchronization primitives." Your shared_mutex sketch calls
coroutine_handle::resume() directly on the unlock thread. That breaks the
invariant. An async_shared_mutex for Capy would need to dispatch through
the executor. it is a reasonable feature request, but your proposed design
is unsafe in Capy's model for the same reason it's unsafe in Asio's model.

6. "Not enough executors." You reviewed only Capy, not Corosio, which
provides the concrete executors. This is the equivalent to reviewing Asio's
executor model without looking at io_context.

7. "resume_on is better than run()." resume_on switches the current
coroutine to a different executor without creating a new environment. run()
creates a new io_env with the correct executor, stop token, and allocator.
resume_on is simpler because it doesn't preserve the invariant across the
transition. It trades away environment propagation for simpler syntax.
That's a valid choice for TMC's model. It's not compatible with Capy's
guarantees.

8. "The awaitable_wrapper can solve this generically." The wrapper calls
resume() directly, doesn't dispatch through the executor, doesn't know
which thread the foreign operation completes on. It breaks the invariant in
exactly the way IoAwaitable prevents.

9. "TMC task runs without an executor." Manually calling
resume()/done()/destroy() on a coroutine_handle. Works in a toy example,
breaks invariants in production code.

10. "Capy is a trap." A trap implies hidden costs. The cost is explicit: a
compile error when you try to bypass the executor. That's the opposite of a
trap.

Thanks
_______________________________________________
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/QVN4UVUC4CK52RGNW5ORMWXXK7CKEGWY/