std::abort_controller
This module provides a WHATWG-style
AbortController / AbortSignal pair for cooperative cancellation across
async functions and OS-thread-backed task concurrency.
The goal is to make cancellation obvious and uniform across the standard library:
- APIs that can be aborted accept an
AbortSignalBorrow(usually as an optional parameter). - Callers create and own an
AbortController, and passcontroller.signal()to the operations they want to be able to abort. - When a signal is aborted, operations stop early and return an
Abortederror-kind in their module’s native error type.
Thread-safety:
AbortSignalstate is protected by a mutex + condition variable.AbortSignalBorrowis a non-owning, copyable handle intended for sharing across OS threads andtaskboundaries.
Exported API#
module std::abort_controller;
import std::interfaces;
import std::memory;
import std::result;
export enum AbortReasonKind {
Aborted,
Message,
}
export struct AbortReason {
kind: AbortReasonKind,
message: string,
}
// Owns the abort state.
export struct AbortController {
signal: AbortSignal,
}
// Owns the abort state (dropped/destroyed by the last owner).
export struct AbortSignal {
handle: u64,
}
// Non-owning, copyable view of an abort signal.
export struct AbortSignalBorrow {
handle: u64,
}
export type AbortControllerResult = std::result::Result(AbortController, std::memory::OutOfMemory);
impl AbortController {
public fn init () -> AbortControllerResult;
public fn signal (self: &AbortController) -> AbortSignalBorrow;
public fn abort (self: &AbortController) -> void;
public fn abort_with_message (self: &AbortController, message: string) -> std::memory::OutOfMemory?;
public fn destroy (mut self: &AbortController) -> void;
}
impl AbortSignal {
public fn invalid () -> AbortSignal;
public fn is_valid (self: &AbortSignal) -> bool;
public fn borrow (self: &AbortSignal) -> AbortSignalBorrow;
public fn is_aborted (self: &AbortSignal) -> bool;
public fn reason (self: &AbortSignal) -> AbortReason?;
public fn wait_fd (self: &AbortSignal) -> int?;
public fn wait (self: &AbortSignal) -> void;
public fn destroy (mut self: &AbortSignal) -> void;
}
impl AbortSignalBorrow {
public fn is_aborted (self: &AbortSignalBorrow) -> bool;
public fn reason (self: &AbortSignalBorrow) -> AbortReason?;
public fn wait_fd (self: &AbortSignalBorrow) -> int?;
public fn wait (self: &AbortSignalBorrow) -> void;
}
Semantics#
AbortController.init()creates an un-aborted signal.AbortController.signal()returns anAbortSignalBorrowthat points to the controller’s underlying signal state.AbortSignalBorrowis non-owning:- it is safe to copy and pass across tasks/threads,
- it becomes invalid once the owning
AbortController/AbortSignalis destroyed or dropped. AbortController.abort()is idempotent: aborting an already-aborted signal is a no-op (the original reason is preserved).abort_with_messageaborts with a user-provided message. If the message cannot be copied due to allocation failure, the signal is still aborted, and the method returnsSome(OutOfMemory{...}).AbortSignal.reason()returns anAbortReasonwhosemessage: stringview is backed by memory owned by the signal. Do not use the returnedmessageafter the signal/controller has been destroyed or dropped.AbortSignalBorrow.wait_fd()returns a file descriptor that becomes readable when the signal is aborted. This is intended forpoll(2)/select(2)style waiting (for example to wait on both a TTY fd and an abort signal at the same time). Do not read from or close the returned fd; it is owned by the signal and used as an internal wake mechanism.AbortSignal.wait()blocks the current OS thread until the signal is aborted. In the Supported forms, this is a blocking synchronization primitive. For select-style waiting, usewait_fd()instead.
Using AbortSignalBorrow Across Tasks#
AbortSignal is an owning, droppable handle. To share a signal across tasks
without transferring ownership, pass a borrow view:
match (std::abort_controller::AbortController.init()) {
Ok(controller) => {
let mut ctl: std::abort_controller::AbortController = controller;
let sig: std::abort_controller::AbortSignalBorrow = ctl.signal();
// Pass `sig` into tasks/operations while `ctl` stays alive.
ctl.destroy();
},
Err(_) => {
// Handle OutOfMemory.
},
}
task fn worker (sig: std::abort_controller::AbortSignalBorrow) -> int {
if sig.is_aborted() { return 0; }
// ...
return 1;
}
This pattern keeps ownership with the creator while allowing other threads to observe and wait on the abort signal.
If a borrow must remain in use after the initialization branch, keep the owning
AbortController alive in an outer binding as well. Do not store only the
AbortSignalBorrow, because the borrow becomes invalid once the owner is
dropped.
Source repository · Edit this page · View Markdown