Expand description
Runtime-independent synchronization primitives: futures and wakers only, usable from any executor. Nuclei’s runtime-independent synchronization primitives: locks, a semaphore, events, a barrier, once-cells and channels that never block a thread.
Every wait in this module is a Future built from
futures and wakers only: async code awaits it, and any executor polls it
with its own Waker. Every primitive also has
try_ forms that never wait. The core of this module makes no system
call: no futex, no thread park, no sleep and no clock read. The one
exception is inside the lock-free queues the primitives use, which recycle
waiter nodes and yield the thread when they meet contention. The other
exception is the
blocking_* methods, which exist for threads that may block and park
the calling thread until their wait is over. Called on a nuclei shard
(inside one of its tasks) they run a nested block_on instead, so the
shard keeps running its other tasks; called inside another runtime’s task
they block the thread that task runs on.
§Names and signatures
Mutex, RwLock, Semaphore, Notify, Barrier,
OnceCell and SetOnce take the names, signatures, error types and
documented behavior of the established async sync API, so code
written against that API compiles against these. The
guards and errors sit at the same paths under nuclei::sync, and the
waits are named futures (Lock, Read, Acquire, and so on) rather
than opaque ones. A semaphore holds at most Semaphore::MAX_PERMITS
permits, which is usize::MAX >> 7. What this module adds is the
Event, the Latch, the Lazy value, the upgradable read of the
RwLock, the reentrant locks and OnceCell::get_or_init_racy.
The mpsc channels follow the same rule for the established channel API,
with their own progress notes.
The local module adds single-shard twins of the
channels, the semaphore and the mutex, with no atomic instruction on any
path, and mesh a full mesh of bounded channels
between a fixed number of parties.
§What every primitive guarantees
- No runtime. A contended acquire queues a waiter, registers the
caller’s
Wakerand returnsPending. What a wake does belongs to the caller’s executor. Within one poll, a contended acquire of a lock or a semaphore may first spin for a short while (up to the lock’s spin budget, a count of pause instructions it adapts to how long holds last, about 5 microseconds at most on the cores it was tuned on): before it queues, and again after a retry it lost. The spin makes no system call, reads no clock, and ends in the same queueing or the samePendingas before. Locks whose holders keep them across long waits of their own do not spin, and their contended callers queue at once: the once-cell’s initializer turn, for example. - Fast under contention. The locks and the semaphore let a caller take what is free even while others wait, so a release never waits for a suspended task to run: the thread that released takes the lock straight back.
- Bounded bypass. A release wakes the oldest waiters it could
serve to compete again. One that loses
MAX_BYPASStimes is owed a handoff: nobody gets in ahead of it, and the next release hands it what it waits for directly. Fairness is counted, never timed. - Cancellation-safe. Dropping a pending future withdraws its
waiter. If something was handed to it concurrently, the drop passes
it on. No wakeup is lost. (A
Barrierwait is the one exception: an arrival that was counted stays counted.) - Cooperative on a nuclei shard. In a task on a nuclei shard (or in
block_on’s own future), a receive ofmpsc,oneshot,broadcastorwatchthat completes spends one operation of the task’s cooperative budget, as IO does (task::coop), and so does an acquire of a semaphore, a lock (the reentrant ones too) or a bounded send that completes at its first attempt. When the budget is spent the call completes nothing, wakes its task and returnsPending, so a loop on a channel or a lock that is always ready yields to the shard’s other tasks. A wait that queued and was granted spends nothing: giving the grant back would cost it its place. Thetry_forms never spend, and off a nuclei shard there is no budget. The events, latches, barriers, once-cells andNotifykeep no budget. - Reentrancy by
Owner. The reentrant locks are keyed by an owner token rather than a thread, because a task can move between threads and many tasks can share one. - Every target. State lives in word-sized atomics (in plain cells
in
local, whose types stay on one thread). On single-threaded wasm the same code runs on one thread, where only interleaved tasks contend. The fast paths allocate nothing; a waiter node comes from a per-primitive free list and reaches the allocator only when that list is empty.
§How a wait works
Each primitive keeps one state word, a lock-free stack of arriving waiters and a FIFO of the waiters already sorted. Uncontended calls touch the state word only, one atomic read-modify-write each. Anything that can make a waiter runnable (a release, a new waiter, a cancellation) takes the drain role with one compare-and-swap, or, when another thread holds it, leaves a mark for the holder and returns, so no call ever waits for another. The holder wakes what the state allows (or, while a handoff is owed, grants it in queue order), rechecks until nothing changed while it worked, and calls the wakers after it has given the role up.
§Progress
Every operation is lock-free, not wait-free. No call waits for another thread, and some call always completes; but a call may do work on behalf of others, and how much depends on them.
- Uncontended, an acquire and a release are one atomic read-modify-write each.
- A call that can make a waiter runnable (a release with waiters, a new
waiter, a cancellation, a notification, a semaphore’s
close, a write guard’sdowngrade,add_permits) may serve the queue itself. It returns after one pass over the queue, plus at most one more pass for each of these that lands while it works: a waiter that queues, a waiter that withdraws, a waiter that claims the handoff it is owed, and a release that finds a node queued. forget_permitswakes nobody: it is a compare-and-swap loop on the state word, which retries only when another call changed the word first. A barrier’s arrival is onefetch_add, followed, for the arrival that completes a group, by the pass that wakes the group.- A release over an empty queue earns no pass; a release that finds a node queued earns one. So releases alone never keep a thread at work. Waiters that keep queueing or withdrawing can, one pass each: every such pass is another thread’s own operation getting done, which is what lock-free means, and there is no cap on the passes of one call.
Besides the primitives, this module re-exports a lock-free map
(HopscotchMap), queues
(SegQueue, ArrayQueue) and
Atom, so one module holds every synchronization structure.
Modules§
- broadcast
- A multi-producer, multi-consumer broadcast channel: each value sent reaches every receiver that exists when it is sent.
- futures
- Named future types, at the conventional
sync::futurespath. - local
- Synchronization primitives for the tasks of one shard.
- mesh
- A full mesh of bounded channels between a fixed number of parties.
- mpsc
- Multi-producer, single-consumer channels for sending values between tasks.
- oneshot
- A channel that carries one value from one sender to one receiver.
- watch
- A multi-producer, multi-consumer channel that retains only the last sent value.
Structs§
- Acquire
- The future
Semaphore::acquireandSemaphore::acquire_manyreturn. - Acquire
Error - Error returned from
Semaphore::acquireand the other waits for permits. - Acquire
Owned - The future
Semaphore::acquire_ownedandSemaphore::acquire_many_ownedreturn. - Array
Queue - A bounded lock-free queue, from the kovan-queue crate: see its docs. A bounded multi-producer multi-consumer queue.
- Atom
- An atomically swapped value, from the kovan crate: see its docs.
A single-value container with safe memory reclamation: loads, stores and
swaps are wait-free, and
rcu()is lock-free (it retries only after another thread’s write). - Barrier
- A barrier that releases its waiters in groups of
n. - Barrier
Wait - The future
Barrier::waitreturns. - Barrier
Wait Result - What a completed
Barrier::waitreports. - Cancellation
Token - A token which can be used to signal a cancellation request to one or more tasks.
- Drop
Guard - A wrapper for a
CancellationTokenwhich cancels it on drop, created byCancellationToken::drop_guard. - Drop
Guard Ref - A wrapper for a borrowed
CancellationTokenwhich cancels it on drop, created byCancellationToken::drop_guard_ref. - Event
- An event that is set once and stays set.
- Event
Wait - The future
Event::waitreturns. - Hopscotch
Map - A concurrent hash map, from the kovan-map crate: see its docs. A concurrent, lock-free hash map based on Hopscotch Hashing.
- Latch
- A latch that opens when its count reaches zero, and stays open.
- Latch
Wait - The future
Latch::waitreturns. - Lazy
- A value built by
Fon first access. - Lock
- The future
Mutex::lockreturns. - Lock
Owned - The future
Mutex::lock_ownedreturns. - Mapped
Mutex Guard - A guard for a component of a
Mutex’s value, made byMutexGuard::map; unlocks on drop. The release makes the progressMutexGuard’s does. - Mutex
- A mutual-exclusion lock whose wait is a future.
- Mutex
Guard - Exclusive access to a
Mutex’s value; unlocks on drop. - Notified
- The future
Notify::notifiedreturns. - Notify
- Wakes tasks waiting for an event that carries no value.
- Once
Cell - A cell that is written at most once.
- Owned
Mapped Mutex Guard - A guard for a component
Uof the value of aMutex<T>held through anArc, made byOwnedMutexGuard::map; unlocks on drop. The release makes the progressMutexGuard’s does. - Owned
Mutex Guard - Exclusive access to a
Mutexheld through anArc; unlocks on drop. The release makes the progressMutexGuard’s does. - Owned
Notified - The future
Notify::notified_ownedreturns:Notifiedthat keeps itsNotifyalive itself. - Owned
RwLock Mapped Write Guard - Exclusive access to a component
Uof the value of aRwLock<T>held through anArc, made byOwnedRwLockWriteGuard::map; releases on drop. The release makes the progressRwLockReadGuard’s does. - Owned
RwLock Read Guard - Shared access to a
RwLockheld through anArc, to its value or to a componentUof it; releases on drop. The release makes the progressRwLockReadGuard’s does. - Owned
RwLock Write Guard - Exclusive access to a
RwLock’s value through anArcthe guard holds; releases on drop. Made byRwLock::write_owned. The release makes the progressRwLockReadGuard’s does. - Owned
Semaphore Permit - Permits taken from a
Semaphoreheld through anArc. The release makes the progressSemaphorePermit’s does. - Owner
- A token that identifies the holder of a reentrant lock.
- Read
- The future
RwLock::readreturns. - Read
Held ByOwner - Why a
ReentrantRwLock::writefailed: its owner reads without writing, so the write would wait for a read only that owner can end. Release the reads first, or take the write before reading. - Read
Owned - The future
RwLock::read_ownedreturns. It keeps its place in the queue, and the bypasses it counted, for as long as it lives; dropping it withdraws the wait and passes on a lock handed to it meanwhile. - Reentrant
Mutex - A mutex the owner holding it can lock again without waiting.
- Reentrant
Mutex Guard - Shared access to a
ReentrantMutex’s value; drops one level of the hold. The drop of the last level releases the lock, which is lock-free, not wait-free: with waiters it returns after one pass over the queue, plus at most one for each operation of another thread that lands on the queue meanwhile (see Progress). - Reentrant
Mutex Lock - The future
ReentrantMutex::lockreturns. - Reentrant
Read - The future
ReentrantRwLock::readreturns. - Reentrant
Read Guard - One read of a
ReentrantRwLock; drops one level of the owner’s reads. The drop of the last level releases the read, which is lock-free, not wait-free: with waiters it returns after one pass over the queue, plus at most one for each operation of another thread that lands on the queue meanwhile (see Progress). - Reentrant
RwLock - A reader-writer lock its holders may lock again, with the bounded
bypass of
RwLock. - Reentrant
Write - The future
ReentrantRwLock::writereturns. - Reentrant
Write Guard - One write of a
ReentrantRwLock; drops one level of the owner’s writes. The drop of the last level releases the write, with the progressReentrantReadGuard’s release makes. - RwLock
- A reader-writer lock whose waits are futures.
- RwLock
Mapped Write Guard - Exclusive access to a component of a
RwLock’s value, made byRwLockWriteGuard::map; releases on drop. The release makes the progressRwLockReadGuard’s does. - RwLock
Read Guard - Shared access to a
RwLock’s value, or to a component of it; releases on drop. - RwLock
Upgradable Read Guard - The upgradable read of a
RwLock: shared access that can become exclusive without a writer getting in between. Releases on drop, with the progressRwLockReadGuard’s release makes. - RwLock
Write Guard - Exclusive access to a
RwLock’s value; releases on drop. The release makes the progressRwLockReadGuard’s does. - SegQueue
- An unbounded lock-free queue and its pop error, from the kovan-queue crate: see its docs. An unbounded, lock-free, linearizable FIFO queue for many producers and many consumers.
- Semaphore
- A counting semaphore whose waits are futures.
- Semaphore
Permit - Permits taken from a
Semaphore, returned when dropped. - SetOnce
- A cell that is set once, and that readers can wait on until it is.
- SetOnce
Error - Error returned from
SetOnce::set: the cell was set already, or anothersetis setting it now. The value comes back. - Thread
Owner - The calling thread’s
Owner, fromOwner::thread. - TryLock
Error - Error returned from
Mutex::try_lock,RwLock::try_readandRwLock::try_writeand their_ownedforms. - Upgradable
Read - The future
RwLock::upgradable_readreturns. - Upgrade
- The future
RwLockUpgradableReadGuard::upgradereturns. Dropping it before it completes releases the upgradable read. - Wait
ForCancellation Future - A future that resolves once the corresponding
CancellationTokenis cancelled. - Wait
ForCancellation Future Owned - A future that resolves once the corresponding
CancellationTokenis cancelled, and owns the token. - Write
- The future
RwLock::writereturns. - Write
Owned - The future
RwLock::write_ownedreturns. It keeps its place in the queue, and the bypasses it counted, for as long as it lives; dropping it withdraws the wait and passes on a handoff made to it meanwhile.
Enums§
- SetError
- Errors that can be returned from
OnceCell::set. - TryAcquire
Error - Error returned from
Semaphore::try_acquireand the other attempts that never wait. - TryPop
Error - An unbounded lock-free queue and its pop error, from the kovan-queue
crate: see its docs.
Why
SegQueue::try_popreturned no value.
Constants§
- MAX_
BYPASS - How many times a waiter can be passed over before it is owed a handoff: a woken waiter that loses its retry this many times is handed the next release directly, and nobody barges past it meanwhile.