Re: [Capy] Review Note

Michael Vandeberg via Boost <[email protected]> Tue, 30 Jun 2026 12:30:50 -0600
Newsgroups gmane.comp.lib.boost.devel
Message-ID <CABD0viZ1mrerXOA5QKFrZXvUY8cXP+o7PeqQ4CSth847HGdD=g@mail.gmail.com>
On Tue, Jun 30, 2026 at 11:06 AM Andrzej Krzemienski <[email protected]>
wrote:

> I am not sure I get this explanation. You say " this is a property of the
> C++20 awaitable contract". It is also the property of the same contract
> that you cannot pass the allocator to the coroutine before the coroutine
> frame is allocated. Yet, Capy overcomes this limitation via the
> thread_local cache. Maybe you could use the same trick for the stop_token?
> I suppose there is a reason not to do it this way. It is clearly an
> engineering trade-off, undocumented.
>
> The docs should have a note that eager awaitables satisfy IoAwaitable, and
> that there is a footgun in that case.
>

The save/restore machinery can probably handle this fine if we stored an
io_env const* in the slot instead of a memory_resource*, so it isn't really
a feasibility question. What matters is what a stale slot costs, and that's
different for the allocator and the stop token.

For the allocator, a stale slot is slow, not wrong. Any memory_resource
hands back usable memory, and operator delete reads the actual resource
back out of the frame footer, so deallocation is correct no matter what the
slot held. That's why we can let run(alloc) swap the allocator and restore
it on scope exit. The worst case is allocating from the wrong pool.

The stop token has no such fallback. If the slot is stale, the operation
sees the wrong cancellation state, and there's nothing like the footer to
recover from, because the right token depends on where you are in the task
tree, not on the frame.

You can see it in the combinators. when_all, when_any, timeout and
cancel_at all forward the caller's frame_allocator untouched, but each one
hands its children a fresh stop token in the same io_env, since the
cancellation scope is part of the structure and the allocator isn't. One
TLS slot suits the allocator: a single value with the occasional explicit,
scoped override. It suits a per-subtree set of tokens far less well, since
those change on their own, with no run()-style call marking the boundary.

So the risk isn't the same in the two cases. The risk trade-off and eager
awaitable footgun are both worth additions to the docs.
_______________________________________________
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/RDL5D4TJHZASLJDZL5Y2QSV2BHJHDPBG/