On Tue, Jun 30, 2026 at 11:06 AM Andrzej Krzemienski <akrzemi1@gmail.com> 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.