Skip to main content
This page is the API reference for Sifr’s concurrency modules. Read the concept pages first if you are new to the model: Async and Await, Structured Tasks, Cancellation and Timeouts, Ownership Across Tasks, and Parallel Work. Every task belongs to a scope. Values that cross task, thread, or process boundaries must be owned and sendable. Errors are typed values you handle explicitly. There is no exposed event-loop object, no global task registry, and no fire-and-forget detachment.

sifr.task — Structured Tasks

sifr.task is the surface for creating, scoping, and coordinating async tasks.

Scoped Spawn

Spawn work inside a task.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 using task.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 using ContextKey[T]:
CPython’s asyncio.get_event_loop(), loop.run_until_complete(), asyncio.ensure_future(), and contextvars global mutation are not available in Sifr. Use task.scope() and explicit ContextKey[T] propagation instead.

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:
For mutable shared state, wrap the value in Lock[T] and access it through a guard:
RwLock[T] allows multiple simultaneous readers or one exclusive writer:
Lock guards and semaphore permits are scoped resources. Holding a guard across an await point or returning one from a scope boundary is rejected by ownership diagnostics.

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:
Closing a sender signals to receivers that no more values will arrive. Queued values drain normally; subsequent sends return 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:
For repeated parallel work over a long-lived process, configure a 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:
signal.signal(), set_wakeup_fd(), and arbitrary handler registration from CPython are not available. Signal delivery is represented as typed values or SignalError, never as process-global mutation.

sifr.process — Subprocesses

Spawn and manage child processes through sifr.process. All handles are owned resources:
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.runtime diagnostics are not CPython warnings or logging global handler mutation. There is no basicConfig, no getLogger, and no handler registration. All diagnostic state is explicit and scoped.

sifr.resource — Deterministic Cleanup

sifr.resource provides nullcontext — an owned-value context manager for deterministic cleanup in structured resource patterns:
Language-level cleanup under task cancellation is part of Sifr’s structured runtime rules: cleanup runs before cancellation evidence is observed by the caller.
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.

Concurrency Overview

Start here for the structured concurrency mental model.

Parallel Work

When to use sifr.parallel instead of async tasks.