Problem
C++ has no async destructors. Async cleanup needs an active driver to resume
suspended work, and destruction can happen with no driver in reach — the two
are structurally incompatible. Not a missing language feature: Rust's
nightly AsyncDrop hits the same wall, scoped to async fn and falling back
to a detached task everywhere else.
New Policy
Async cleanup is explicit. Any object needing async cleanup will provide a shutdown() coroutine API. Libraries
implement it as a coroutine, and it's the method name the framework's
concepts (has_shutdown) looks for. Async cleanup is always a coroutine, driven by
whatever already drives the context, never a destructor.
- Voluntary exit and involuntary cancellation are both solvable this way since destruction happens normally as apart of the normal resume() call.
- The case of
~context() will need to cancel without cleanup rather than blocking the thread to cleanly cancel the operations.
Sub-issues
Problem
C++ has no async destructors. Async cleanup needs an active driver to resume
suspended work, and destruction can happen with no driver in reach — the two
are structurally incompatible. Not a missing language feature: Rust's
nightly
AsyncDrophits the same wall, scoped toasync fnand falling backto a detached task everywhere else.
New Policy
Async cleanup is explicit. Any object needing async cleanup will provide a
shutdown()coroutine API. Librariesimplement it as a coroutine, and it's the method name the framework's
concepts (
has_shutdown) looks for. Async cleanup is always a coroutine, driven bywhatever already drives the context, never a destructor.
~context()will need to cancel without cleanup rather than blocking the thread to cleanly cancel the operations.Sub-issues
context::cancel()→cancel_without_cleanup()is_cleanupbool field topromise_basecontext::async_cancel(): coroutine, walks the continuation chain bottom-up,resumes/skips cleanup coroutines, cancels each real frame in turn,
repeats until the top-level future is cancelled. Resolves the
blocked_by::syncdeadlock (driven byresume(), not a spin loop).promise_base, checked byasync_cancel's walkand by
final_awaiter— one mechanism for cancellation and normalexit.
the original frame's stack memory is reclaimed (allocator is strict
LIFO).
async_cleanup<T>: no-op destructor, real destruction deferred toshutdown()via the final-awaiter drain.async_cleanup<void>: same as above without a wrapped object. Holds acallback coroutine factory, invoked at final suspend time.
has_shutdownconcept +shutdown_allfold pattern, for framesholding more than one cleanup object.
no-scheduler) and the policy for each.
multi-resource cleanup ordering; LIFO frame-free regression.