Skip to content

Define cooperative cancellation and its relation to shutdown #223

Description

@tisonkun

Background

Asyncband's shutdown primitive combines two mechanisms:

  1. a sticky cooperative request that every receiver or watcher can observe; and
  2. 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:

  1. extract a public cancellation signal and build graceful shutdown from cancellation plus WaitGroup-style participant accounting;
  2. add a separate cancellation primitive while leaving shutdown internally unchanged; or
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions