Background
Asyncband's shutdown primitive combines two mechanisms:
- a sticky cooperative request that every receiver or watcher can observe; and
- completion tracking that lets the coordinator wait for participating receiver handles to be dropped.
Issue #193 concluded that this combined protocol is correctly described as graceful shutdown, while cancellation is the established name for the signal-only part. A standalone cancellation primitive may still be useful for operations that need a cloneable stop signal but do not need participant accounting or a graceful-shutdown coordinator.
Tokio Util's CancellationToken, .NET's CancellationToken, C++ stop_token, and the web platform's AbortSignal demonstrate the common core: cancellation is monotonic, cooperative, observable by many consumers, and does not itself interrupt or own their work.
Tracked by #218.
Question
Should Asyncband expose the signal half as a general primitive, and if so, should shutdown be implemented in terms of it or remain an independent composed protocol?
The issue should answer this product and factoring question before committing to another public type.
Proposed minimum contract
If accepted, the initial cancellation mechanism should provide separate capabilities, provisionally named CancellationSource and CancellationToken:
- the source can request cancellation;
- tokens are cloneable observers and cannot request cancellation;
- cancellation is sticky and idempotent;
- a token can query an immediate snapshot and obtain a future that completes once cancellation is requested;
- token waits are cancel safe and may be recreated without consuming the signal;
- no task is spawned, interrupted, dropped, or otherwise controlled by the primitive;
- the primitive does not imply that dropping a future cancels any work behind a spawned-task handle.
An owned cancellation future should be considered so the wait can move independently of a borrowed token, consistent with ShutdownWatch::is_shutdown_owned.
Design considerations
Relationship with shutdown
There are three viable directions:
- extract a public cancellation signal and build graceful shutdown from cancellation plus
WaitGroup-style participant accounting;
- add a separate cancellation primitive while leaving
shutdown internally unchanged; or
- expose no new primitive because
ShutdownWatch already supplies sufficient signal-only observation.
The first direction reduces semantic duplication but could make a stable shutdown implementation depend on a new abstraction and migration. The second minimizes code churn but duplicates state and wake-up machinery. The third keeps the API smaller but forces users to construct a graceful-shutdown pair when they only need a signal.
The decision should be based on caller-facing demand and contract clarity, not only potential internal reuse.
Source cardinality and drop
A single non-cloneable source gives cancellation ownership a clear location. A cloneable source allows any controller to cancel but makes source-drop behavior harder to describe.
Dropping the final source should not implicitly request cancellation unless that behavior is explicitly part of the type contract. Implicit cancellation is convenient for structured lifetimes but makes accidental controller drop observable as a cancellation request. The preferred minimum is explicit cancellation; source drop should instead close control capability, with token behavior documented separately.
Token hierarchy and composition
Tokio supports child tokens; the web platform can derive a signal from several inputs. Both features introduce additional semantics:
- whether parent cancellation propagates only downward;
- whether cancelling a child affects siblings or the parent;
- how child state is retained and reclaimed;
- which cancellation reason wins when signals are combined.
Children, any, and reason propagation should be deferred until the single shared signal has a proven use case and contract.
Cancellation reason
A boolean signal is compact and matches the existing shutdown request. Adding a reason improves diagnostics and mirrors AbortSignal, but it requires shared ownership of an arbitrary value and a deterministic winner among concurrent requests. A reason should not be part of the first version unless a concrete caller needs it.
Wait registration and cancellation safety
Registering a waker and checking the sticky state must not lose a concurrent cancellation request. Dropping a pending wait removes only that registration. Once cancellation is committed, all current and future waits complete.
Naming
CancellationToken is familiar, but a source/token split differs from Tokio's cloneable token where every clone may call cancel. The names and capabilities should make authority obvious. Async methods should avoid an is_ prefix; use an immediate is_cancelled() predicate and a waiting operation such as cancelled().await.
Key trade-offs
- A smaller public API versus avoiding misuse of the richer
shutdown protocol for signal-only needs.
- Internal reuse by
shutdown versus preserving its existing implementation and compatibility independently.
- Source/token authority separation versus a single cloneable token API.
- Explicit cancellation on source drop versus lifetime-driven implicit cancellation.
- A boolean first version versus cancellation reasons, hierarchy, and composition.
Non-goals
The first version should not provide:
- forced interruption or preemption;
- task spawning, task ownership, or task groups;
- deadlines, timeout constructors, or timer integration;
- callbacks executed on an executor;
- child tokens, token composition, or cancellation reasons unless separately justified;
- automatic cancellation of an underlying operation when a task handle is dropped.
Acceptance criteria
Before implementation, the issue must establish:
- a concrete signal-only use case not adequately served by
shutdown;
- the selected relationship between cancellation and graceful shutdown;
- source cardinality and source-drop behavior;
- the exact sticky-state, query, wait, and cancellation-safety contract;
- names that distinguish immediate queries from asynchronous waiting.
If implementation is accepted:
- all current and future token waits observe a committed cancellation request;
- cancelling or dropping one wait does not affect other observers;
- races among wait registration, source cancellation, source drop, and token drop have deterministic tests;
- no executor, timer, or runtime-specific dependency is introduced;
- repository build, test, lint, and formatting workflows pass through
cargo x.
Background
Asyncband's
shutdownprimitive combines two mechanisms:Issue #193 concluded that this combined protocol is correctly described as graceful shutdown, while cancellation is the established name for the signal-only part. A standalone cancellation primitive may still be useful for operations that need a cloneable stop signal but do not need participant accounting or a graceful-shutdown coordinator.
Tokio Util's
CancellationToken, .NET'sCancellationToken, C++stop_token, and the web platform'sAbortSignaldemonstrate the common core: cancellation is monotonic, cooperative, observable by many consumers, and does not itself interrupt or own their work.Tracked by #218.
Question
Should Asyncband expose the signal half as a general primitive, and if so, should
shutdownbe implemented in terms of it or remain an independent composed protocol?The issue should answer this product and factoring question before committing to another public type.
Proposed minimum contract
If accepted, the initial cancellation mechanism should provide separate capabilities, provisionally named
CancellationSourceandCancellationToken:An owned cancellation future should be considered so the wait can move independently of a borrowed token, consistent with
ShutdownWatch::is_shutdown_owned.Design considerations
Relationship with
shutdownThere are three viable directions:
WaitGroup-style participant accounting;shutdowninternally unchanged; orShutdownWatchalready supplies sufficient signal-only observation.The first direction reduces semantic duplication but could make a stable shutdown implementation depend on a new abstraction and migration. The second minimizes code churn but duplicates state and wake-up machinery. The third keeps the API smaller but forces users to construct a graceful-shutdown pair when they only need a signal.
The decision should be based on caller-facing demand and contract clarity, not only potential internal reuse.
Source cardinality and drop
A single non-cloneable source gives cancellation ownership a clear location. A cloneable source allows any controller to cancel but makes source-drop behavior harder to describe.
Dropping the final source should not implicitly request cancellation unless that behavior is explicitly part of the type contract. Implicit cancellation is convenient for structured lifetimes but makes accidental controller drop observable as a cancellation request. The preferred minimum is explicit cancellation; source drop should instead close control capability, with token behavior documented separately.
Token hierarchy and composition
Tokio supports child tokens; the web platform can derive a signal from several inputs. Both features introduce additional semantics:
Children,
any, and reason propagation should be deferred until the single shared signal has a proven use case and contract.Cancellation reason
A boolean signal is compact and matches the existing shutdown request. Adding a reason improves diagnostics and mirrors
AbortSignal, but it requires shared ownership of an arbitrary value and a deterministic winner among concurrent requests. A reason should not be part of the first version unless a concrete caller needs it.Wait registration and cancellation safety
Registering a waker and checking the sticky state must not lose a concurrent cancellation request. Dropping a pending wait removes only that registration. Once cancellation is committed, all current and future waits complete.
Naming
CancellationTokenis familiar, but a source/token split differs from Tokio's cloneable token where every clone may callcancel. The names and capabilities should make authority obvious. Async methods should avoid anis_prefix; use an immediateis_cancelled()predicate and a waiting operation such ascancelled().await.Key trade-offs
shutdownprotocol for signal-only needs.shutdownversus preserving its existing implementation and compatibility independently.Non-goals
The first version should not provide:
Acceptance criteria
Before implementation, the issue must establish:
shutdown;If implementation is accepted:
cargo x.