sifr.task — Structured Tasks
sifr.task is the surface for creating, scoping, and coordinating async tasks.
Scoped Spawn
Spawn work inside atask.scope() block. The scope keeps ownership of all spawned handles and guarantees cleanup before the block exits — even under cancellation:
TaskHandle[T, E] is a linear ownership value. Awaiting or joining a handle consumes it; the compiler rejects any attempt to use the handle again.
Timeout and Deadline
Bound the runtime of any awaitable expression usingtask.timeout (duration-based) or task.deadline (absolute time):
TaskGroup
TaskGroup collects a set of tasks and waits for all of them, propagating the first error. Use it as an async context manager:
Context Propagation
Pass typed context values across task boundaries usingContextKey[T]:
sifr.sync — Channels and Locks
sifr.sync provides same-process communication and synchronization primitives. All values that cross a task boundary must satisfy Sifr’s sendability requirements — the compiler enforces this at the call site.
Shared State
Shared[T] holds an immutable value that can be read from any task without a lock:
Lock[T] and access it through a guard:
RwLock[T] allows multiple simultaneous readers or one exclusive writer:
Channels
Channels are the primary way to move ownership of values between tasks.channel() creates an unbounded channel; bounded_channel(n) creates one that applies backpressure at capacity n:
Err(ClosedError):
Semaphore and Notify
Semaphore limits concurrent access to a resource. Notify is a lightweight one-shot or broadcast signal:
Full Channel Demo
The following is the complete sync-channel demo from the Sifr repository, showing unbounded channels, bounded channels with backpressure, sender close semantics, and cancellation safety:sifr.parallel — CPU Parallelism
sifr.parallel runs CPU-heavy work across native worker threads. Use it for compute-intensive maps over large datasets when async concurrency alone is not enough:
Pool explicitly:
Values passed into and returned from
pool.map must satisfy worker-boundary sendability. Non-send resources (lock guards, task handles, borrowed values) are rejected by the compiler at the call site.sifr.signal — Shutdown Signals
React to OS signals with structured, awaitable values rather than global handler mutation:
sifr.process — Subprocesses
Spawn and manage child processes through sifr.process. All handles are owned resources:
- Synchronous
- Async
- With Timeout
subprocess.Popen is not available. Shell execution is an explicit opt-in through run_shell and output_shell, not the default path.sifr.runtime — Runtime Diagnostics
sifr.runtime provides structured diagnostic events for observability around task, sync, process, signal, and IPC surfaces. Diagnostic emission is an explicit, typed operation — not a global logging side channel.
emit_diagnostic returns Result[None, DiagnosticError] — call sites handle the result explicitly. Payload bytes, process command lines, environment values, and decoded IPC payloads must not be used as diagnostic messages unless an explicit redaction rule applies.
sifr.resource — Deterministic Cleanup
sifr.resource provides nullcontext — an owned-value context manager for deterministic cleanup in structured resource patterns:
ExitStack, AsyncExitStack, closing, and aclosing are unsupported. Cleanup helpers do not provide a dynamic stack of arbitrary callbacks; all resource lifetimes are visible in the ownership graph.Related Concept Pages
Concurrency Overview
Start here for the structured concurrency mental model.
Parallel Work
When to use
sifr.parallel instead of async tasks.