nuclei/sync/mod.rs
1//! Nuclei's runtime-independent synchronization primitives: locks, a
2//! semaphore, events, a barrier, once-cells and channels that never block a
3//! thread.
4//!
5//! Every wait in this module is a [`Future`] built from
6//! futures and wakers only: async code awaits it, and any executor polls it
7//! with its own [`Waker`](core::task::Waker). Every primitive also has
8//! `try_` forms that never wait. The core of this module makes no system
9//! call: no futex, no thread park, no sleep and no clock read. The one
10//! exception is inside the lock-free queues the primitives use, which recycle
11//! waiter nodes and yield the thread when they meet contention. The other
12//! exception is the
13//! `blocking_*` methods, which exist for threads that may block and park
14//! the calling thread until their wait is over. Called on a nuclei shard
15//! (inside one of its tasks) they run a nested `block_on` instead, so the
16//! shard keeps running its other tasks; called inside another runtime's task
17//! they block the thread that task runs on.
18//!
19//! # Names and signatures
20//!
21//! [`Mutex`](crate::sync::Mutex), [`RwLock`](crate::sync::RwLock), [`Semaphore`](crate::sync::Semaphore), [`Notify`](crate::sync::Notify), [`Barrier`](crate::sync::Barrier),
22//! [`OnceCell`](crate::sync::OnceCell) and [`SetOnce`](crate::sync::SetOnce) take the names, signatures, error types and
23//! documented behavior of the established async `sync` API, so code
24//! written against that API compiles against these. The
25//! guards and errors sit at the same paths under `nuclei::sync`, and the
26//! waits are named futures (`Lock`, `Read`, `Acquire`, and so on) rather
27//! than opaque ones. A semaphore holds at most [`Semaphore::MAX_PERMITS`](crate::sync::Semaphore::MAX_PERMITS)
28//! permits, which is `usize::MAX >> 7`. What this module adds is the
29//! [`Event`](crate::sync::Event), the [`Latch`](crate::sync::Latch), the [`Lazy`](crate::sync::Lazy) value, the upgradable read of the
30//! [`RwLock`](crate::sync::RwLock), the reentrant locks and `OnceCell::get_or_init_racy`.
31//! The [`mpsc`](crate::sync::mpsc) channels follow the same rule for the established channel API,
32//! with their own progress notes.
33//! The [`local`](crate::sync::local) module adds single-shard twins of the
34//! channels, the semaphore and the mutex, with no atomic instruction on any
35//! path, and [`mesh`](crate::sync::mesh) a full mesh of bounded channels
36//! between a fixed number of parties.
37//!
38//! # What every primitive guarantees
39//!
40//! - **No runtime.** A contended acquire queues a waiter, registers the
41//! caller's `Waker` and returns `Pending`. What a wake does belongs to
42//! the caller's executor. Within one poll, a contended acquire of a lock
43//! or a semaphore may first spin for a short while (up to the lock's
44//! spin budget, a count of pause instructions it adapts to how long holds
45//! last, about 5 microseconds at most on the cores it was tuned on):
46//! before it queues, and again after a retry it lost. The spin makes no
47//! system call, reads no clock, and ends in the same queueing or the same
48//! `Pending` as before. Locks whose holders keep them across long waits
49//! of their own do not spin, and their contended callers queue at once:
50//! the once-cell's initializer turn, for example.
51//! - **Fast under contention.** The locks and the semaphore let a caller
52//! take what is free even while others wait, so a release never waits
53//! for a suspended task to run: the thread that released takes the lock
54//! straight back.
55//! - **Bounded bypass.** A release wakes the oldest waiters it could
56//! serve to compete again. One that loses [`MAX_BYPASS`](crate::sync::MAX_BYPASS) times is owed a
57//! handoff: nobody gets in ahead of it, and the next release hands it
58//! what it waits for directly. Fairness is counted, never timed.
59//! - **Cancellation-safe.** Dropping a pending future withdraws its
60//! waiter. If something was handed to it concurrently, the drop passes
61//! it on. No wakeup is lost. (A [`Barrier`](crate::sync::Barrier) wait is the one exception: an
62//! arrival that was counted stays counted.)
63//! - **Cooperative on a nuclei shard.** In a task on a nuclei shard (or in
64//! `block_on`'s own future), a receive of [`mpsc`](crate::sync::mpsc),
65//! [`oneshot`](crate::sync::oneshot), [`broadcast`](crate::sync::broadcast)
66//! or [`watch`](crate::sync::watch) that completes spends one operation of
67//! the task's cooperative budget, as IO does
68//! ([`task::coop`]), and so does an acquire of a
69//! semaphore, a lock (the reentrant ones too) or a bounded send that
70//! completes at its first attempt. When the budget is spent the call
71//! completes nothing, wakes its task and returns `Pending`, so a loop on a
72//! channel or a lock that is always ready yields to the shard's other
73//! tasks. A wait that queued and was granted spends nothing: giving the
74//! grant back would cost it its place. The `try_` forms never spend, and
75//! off a nuclei shard there is no budget. The events, latches, barriers,
76//! once-cells and `Notify` keep no budget.
77//! - **Reentrancy by [`Owner`](crate::sync::Owner).** The reentrant locks are keyed by an
78//! owner token rather than a thread, because a task can move between
79//! threads and many tasks can share one.
80//! - **Every target.** State lives in word-sized atomics (in plain cells
81//! in [`local`](crate::sync::local), whose types stay on one thread). On
82//! single-threaded wasm the same code runs on one thread, where only
83//! interleaved tasks contend. The fast paths allocate nothing; a waiter
84//! node comes from a per-primitive free list and reaches the allocator
85//! only when that list is empty.
86//!
87//! # How a wait works
88//!
89//! Each primitive keeps one state word, a lock-free stack of arriving
90//! waiters and a FIFO of the waiters already sorted. Uncontended calls
91//! touch the state word only, one atomic read-modify-write each. Anything
92//! that can make a waiter runnable (a release, a new waiter, a
93//! cancellation) takes the drain role with one compare-and-swap, or, when
94//! another thread holds it, leaves a mark for the holder and returns, so
95//! no call ever waits for another. The holder wakes what the state allows
96//! (or, while a handoff is owed, grants it in queue order), rechecks until
97//! nothing changed while it worked, and calls the wakers after it has
98//! given the role up.
99//!
100//! # Progress
101//!
102//! Every operation is lock-free, not wait-free. No call waits for another
103//! thread, and some call always completes; but a call may do work on
104//! behalf of others, and how much depends on them.
105//!
106//! - Uncontended, an acquire and a release are one atomic
107//! read-modify-write each.
108//! - A call that can make a waiter runnable (a release with waiters, a new
109//! waiter, a cancellation, a notification, a semaphore's `close`, a write
110//! guard's `downgrade`, `add_permits`) may serve the queue itself. It
111//! returns after one pass over the queue, plus at most one more pass for
112//! each of these that lands while it works: a waiter that queues, a
113//! waiter that withdraws, a waiter that claims the handoff it is owed, and
114//! a release that finds a node queued.
115//! - `forget_permits` wakes nobody: it is a compare-and-swap loop on the
116//! state word, which retries only when another call changed the word
117//! first. A barrier's arrival is one `fetch_add`, followed, for the
118//! arrival that completes a group, by the pass that wakes the group.
119//! - A release over an empty queue earns no pass; a release that finds a
120//! node queued earns one. So releases alone never keep a thread at work.
121//! Waiters that keep queueing or withdrawing can, one pass each: every
122//! such pass is another thread's own operation getting done, which is
123//! what lock-free means, and there is no cap on the passes of one call.
124//!
125//! Besides the primitives, this module re-exports a lock-free map
126//! ([`HopscotchMap`](crate::sync::HopscotchMap)), queues
127//! ([`SegQueue`](crate::sync::SegQueue), [`ArrayQueue`](crate::sync::ArrayQueue)) and
128//! [`Atom`](crate::sync::Atom), so one module holds every synchronization structure.
129
130/// Defines a constructor as a `const fn` in an ordinary build and as a
131/// plain `fn` under `--cfg loom`, whose instrumented atomics cannot be
132/// built in a constant context.
133macro_rules! loom_const_fn {
134 ($(#[$attr:meta])* $vis:vis fn $name:ident($($arg:ident : $ty:ty),* $(,)?) -> $ret:ty $body:block) => {
135 $(#[$attr])*
136 #[cfg(not(all(loom, test)))]
137 $vis const fn $name($($arg: $ty),*) -> $ret $body
138
139 $(#[$attr])*
140 #[cfg(all(loom, test))]
141 $vis fn $name($($arg: $ty),*) -> $ret $body
142 };
143}
144
145#[cfg(not(all(loom, test)))]
146pub(crate) mod atomic_waker;
147#[cfg(all(loom, test))]
148#[doc(hidden)]
149pub mod atomic_waker;
150mod barrier;
151pub mod broadcast;
152mod cancellation;
153mod contend;
154mod event;
155mod fair_semaphore;
156pub(crate) mod internal;
157mod latch;
158mod lazy;
159mod list;
160pub mod local;
161#[cfg(all(loom, test))]
162#[doc(hidden)]
163pub(crate) mod loom_model;
164#[cfg(all(test, loom))]
165mod loom_tests;
166pub mod mesh;
167pub mod mpsc;
168mod mutex;
169mod notify;
170mod once_cell;
171pub mod oneshot;
172// See the note on its re-export below.
173#[allow(dead_code)]
174mod owned_lock;
175mod owner;
176mod queue;
177mod raw_rwlock;
178mod raw_semaphore;
179mod reentrant_mutex;
180mod reentrant_rwlock;
181mod rwlock;
182mod semaphore;
183mod set_once;
184mod spin;
185#[cfg(all(test, not(loom)))]
186pub(crate) mod test_support;
187mod waiter;
188pub mod watch;
189
190pub use barrier::{Barrier, BarrierWait, BarrierWaitResult};
191pub use cancellation::{
192 CancellationToken, DropGuard, DropGuardRef, WaitForCancellationFuture,
193 WaitForCancellationFutureOwned,
194};
195pub use contend::MAX_BYPASS;
196pub use event::{Event, EventWait};
197pub use latch::{Latch, LatchWait};
198pub use lazy::Lazy;
199pub use mutex::{
200 Lock, LockOwned, MappedMutexGuard, Mutex, MutexGuard, OwnedMappedMutexGuard, OwnedMutexGuard,
201 TryLockError,
202};
203pub use notify::{Notified, Notify, OwnedNotified};
204pub use once_cell::{OnceCell, SetError};
205// vertexia: the owner-named lock has no consumer in this crate yet; its
206// tests run in `owned_lock`. Trim it if it stays unused.
207#[cfg(test)]
208#[allow(unused_imports)]
209pub(crate) use owned_lock::between_take_and_grant;
210pub use owner::{Owner, ThreadOwner};
211pub use reentrant_mutex::{ReentrantMutex, ReentrantMutexGuard, ReentrantMutexLock};
212pub use reentrant_rwlock::{
213 ReadHeldByOwner, ReentrantRead, ReentrantReadGuard, ReentrantRwLock, ReentrantWrite,
214 ReentrantWriteGuard,
215};
216pub use rwlock::{
217 OwnedRwLockMappedWriteGuard, OwnedRwLockReadGuard, OwnedRwLockWriteGuard, Read, ReadOwned,
218 RwLock, RwLockMappedWriteGuard, RwLockReadGuard, RwLockUpgradableReadGuard, RwLockWriteGuard,
219 UpgradableRead, Upgrade, Write, WriteOwned,
220};
221pub use semaphore::{
222 Acquire, AcquireError, AcquireOwned, OwnedSemaphorePermit, Semaphore, SemaphorePermit,
223 TryAcquireError,
224};
225pub use set_once::{SetOnce, SetOnceError};
226
227/// Named future types, at the conventional `sync::futures` path.
228pub mod futures {
229 pub use super::notify::{Notified, OwnedNotified};
230}
231
232// These four are the kovan crates' own types, re-exported as they are: their
233// API and its changes are kovan's, and code that names them through this
234// module follows kovan's versions, which nuclei's own semver does not cover.
235/// An atomically swapped value, from the kovan crate: see its docs.
236pub use kovan::Atom;
237/// A concurrent hash map, from the kovan-map crate: see its docs.
238pub use kovan_map::HopscotchMap;
239/// A bounded lock-free queue, from the kovan-queue crate: see its docs.
240pub use kovan_queue::array_queue::ArrayQueue;
241/// An unbounded lock-free queue and its pop error, from the kovan-queue
242/// crate: see its docs.
243pub use kovan_queue::seg_queue::{SegQueue, TryPopError};
244
245#[cfg(all(test, not(loom)))]
246mod tests {
247 use super::*;
248
249 fn send_sync<T: Send + Sync>() {}
250
251 fn future_is_send_sync<F: core::future::Future + Send + Sync>(_: &F) {}
252
253 #[test]
254 fn handles_futures_and_guards_are_send_and_sync() {
255 send_sync::<Mutex<u8>>();
256 send_sync::<Lock<'static, u8>>();
257 send_sync::<LockOwned<u8>>();
258 send_sync::<MutexGuard<'static, u8>>();
259 send_sync::<OwnedMutexGuard<u8>>();
260 send_sync::<MappedMutexGuard<'static, u8>>();
261 send_sync::<OwnedMappedMutexGuard<u8, u8>>();
262 send_sync::<RwLock<u8>>();
263 send_sync::<Read<'static, u8>>();
264 send_sync::<ReadOwned<u8>>();
265 send_sync::<Write<'static, u8>>();
266 send_sync::<RwLockReadGuard<'static, u8>>();
267 send_sync::<RwLockWriteGuard<'static, u8>>();
268 send_sync::<RwLockMappedWriteGuard<'static, u8>>();
269 send_sync::<WriteOwned<u8>>();
270 send_sync::<OwnedRwLockReadGuard<u8, u8>>();
271 send_sync::<OwnedRwLockWriteGuard<u8>>();
272 send_sync::<OwnedRwLockMappedWriteGuard<u8, u8>>();
273 send_sync::<RwLockUpgradableReadGuard<'static, u8>>();
274 send_sync::<UpgradableRead<'static, u8>>();
275 send_sync::<Upgrade<'static, u8>>();
276 send_sync::<ReadHeldByOwner>();
277 send_sync::<Semaphore>();
278 send_sync::<Acquire<'static>>();
279 send_sync::<AcquireOwned>();
280 send_sync::<SemaphorePermit<'static>>();
281 send_sync::<OwnedSemaphorePermit>();
282 send_sync::<Notify>();
283 send_sync::<Notified<'static>>();
284 send_sync::<OwnedNotified>();
285 send_sync::<TryLockError>();
286 send_sync::<AcquireError>();
287 send_sync::<TryAcquireError>();
288 send_sync::<SetError<u8>>();
289 send_sync::<SetOnceError<u8>>();
290 send_sync::<SetOnce<u8>>();
291 send_sync::<Event>();
292 send_sync::<EventWait<'static>>();
293 send_sync::<Latch>();
294 send_sync::<LatchWait<'static>>();
295 send_sync::<Barrier>();
296 send_sync::<BarrierWait<'static>>();
297 send_sync::<OnceCell<u8>>();
298 send_sync::<Lazy<u8>>();
299 send_sync::<ReentrantMutex<core::cell::RefCell<u8>>>();
300 send_sync::<ReentrantRwLock<u8>>();
301 // An owner moves with its task but is never shared (see `Owner`).
302 send::<Owner>();
303 // A value that may be shared but not moved: the upgradable read
304 // can still be shared, and its doc test pins that it cannot move.
305 sync::<RwLockUpgradableReadGuard<'static, Option<std::sync::MutexGuard<'static, ()>>>>();
306 }
307
308 fn send<T: Send>() {}
309
310 fn sync<T: Sync>() {}
311
312 #[test]
313 fn async_entry_points_return_send_and_sync_futures() {
314 let cell = OnceCell::<u8>::new();
315 future_is_send_sync(&cell.get_or_init(|| async { 1 }));
316 future_is_send_sync(&cell.get_or_try_init(|| async { Ok::<u8, ()>(1) }));
317 let once = SetOnce::<u8>::new();
318 future_is_send_sync(&once.wait());
319 }
320}