Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Concurrent runtime mechanisms

Worker eligibility is a closed-world compiler check over finite type graphs, reachable helpers, concrete callbacks, and destructors. Visited identities bound recursive walks. Atomically counted owners can cross certified boundaries; counts alone do not permit mutable sharing. The concurrency contracts define allowed effects, owned jobs, blocking, and shutdown.

Each native primitive (CollectionOp) reports an effect from one exhaustive match in src/effects.rs; the certification walk rejects a worker that can reach a denied effect and names the call. Primitives without a native contract, such as foreign calls, are rejected separately. See native effects on worker threads.

Tasks and queues

Scoped tasks pin argument/result storage in the parent’s frame. Generated C adapters invoke typed workers and publish results there. POSIX join must complete before reclaiming storage; unexpected join failure terminates the process to avoid a worker using departed storage. Task bookkeeping does not allocate a Plenty owner; native thread stacks and OS bookkeeping still use resources.

Channels allocate a pinned core/ring whose slots reserve complete message layouts. Endpoint values reference counted headers. One mutex protects predicates and disconnect flags; separate condition variables serve readers and writers. Disconnect wakes all waiters. Receive relocates values before slot reuse, and user destructors run outside locks. nowait can still contend on a mutex.

Executors keep pinned worker records and a bounded ring. Partial startup failure joins started workers. Each accepted job has one fallibly allocated cell for its concrete environment and result; queue/worker and future owners use separate atomic counts. Queue and job state have separate locks. User callbacks/destructors run outside them; pool destruction drains and joins.

Cancellation and selection

Tokens combine an atomic cancellation flag with a mutex/condition-variable handshake for waiter registration. Timed waits track a monotonic elapsed budget, so wakeups do not restart it and huge millisecond values need no overflowing absolute deadline.

Receive selection registers pinned stack waiter nodes in participating queues and uses a per-call condition variable. Queue-to-waiter lock ordering synchronizes notification; selection transfers a value under its queue lock and removes all registrations on every return. It needs no global selection lock or per-wait heap allocation.

Atomics

std.atomic operations compile inline, with no runtime call. A method borrows its receiver’s place, so a local atomic lives in the function’s frame and every thread addresses the same slot. The payload is the slot’s low word, naturally aligned because slots are at least 8-byte aligned. Each access is a Cranelift atomic instruction (atomic_load, atomic_store, atomic_rmw, atomic_cas, fence) of the element’s exact width, never a plain load the optimizer could merge. Checked fetch_add/fetch_sub loop on atomic_cas and trap before storing. Cranelift’s atomics are all sequentially consistent, so each requested Ordering is validated at compile time and then strengthened. On x86-64 a load is a mov, a store and a fence add mfence, and read-modify-write operations use lock-prefixed instructions or a lock cmpxchg loop.

Mutexes

std.sync.Mutex[T] is the generic class in stdlib/std/sync.plenty: an Atomic[u32] state word and the value, stored inline, so moves, destruction, and worker eligibility are ordinary class behavior. Its fields are private; lock() and into_inner() are compiler intrinsics. A lock block stores the mutex reference in a hidden guard local and roots the value’s exclusive loan there, while the mutex’s own shared loan stays live until the block’s exit. The ownership checker treats the guard as live from the lock to the unlock and rejects a join reached while a live guard’s T matches a Mutex[T] the worker’s inputs can reach. Each pool submission records the Mutex[T] types its job input can reach, beside the sender message types, in the function’s submission list and in the future’s local provenance; result() and each shutdown run the same guard scan against them. Reachability walks through Shared[...] contents.

Shared owners

Shared[T] reuses the Box[T] runtime record and its type metadata kind: a counted header, the content type, and a 32-byte-aligned content slot. The frontend emits BoxOp::Share (the box allocation entry point) and BoxOp::SharedRef, a shared-only projection to the content slot; it never emits the box’s move-out or mutable projection for a handle. share() is the ordinary retain of a loaded owner. Construction rejects contents that reach an executor or future, or that reach the same Shared[T] through a Mutex or a channel endpoint.

Locking follows mutex 2 of Drepper’s “Futexes Are Tricky”: state 0 is unlocked, 1 locked, and 2 locked with possible waiters. Lock is an inline atomic_cas from 0 to 1. On failure, plenty_mutex_lock_slow exchanges in 2 and, until the exchange returns 0, sleeps with FUTEX_WAIT_PRIVATE on value 2; EINTR and EAGAIN retry the exchange. Unlock is an inline atomic_rmw exchange to 0, and calls plenty_mutex_wake (FUTEX_WAKE_PRIVATE of one waiter) only when it replaced 2. The runtime issues the futex system call directly. Nothing allocates; a held lock is never abandoned by unwinding, because traps terminate the process.