Expand description
A concurrent intrusive list for building synchronization primitives.
Maillon is the French word for a chain link.
§Features
- 100% safe API
#![no_std], no allocation- Atomic emptiness check to avoid acquiring the mutex if the list is empty
- Lock-free insertion: multiple nodes can be inserted concurrently while another is being removed; removal requires locking
- Optional atomic state embedded in the list when empty (to carry a semaphore counter, a closed flag, etc.)
WaitList, a high-level asynchronous wait list with customizable synchronization built on top of the low-levelList
§Usage
WaitList is a ready-to-use asynchronous wait list, built on top of List:
use std::sync::atomic::{AtomicBool, Ordering::Relaxed};
use maillon::WaitList;
#[derive(Default)]
pub struct Event {
done: AtomicBool,
wait_list: WaitList,
}
impl Event {
pub async fn wait(&self) {
let _ = self.wait_list.wait_until(|_| self.done.load(Relaxed)).await;
}
pub fn set(&self) {
self.done.store(true, Relaxed);
self.wait_list.notify_all();
}
}List is the building block: Nodes are pinned, carry user data implementing
NodeData, and are pushed to the back without locking, while every other operation goes
through List::lock. Here is a minimal wait list, supporting only notify_one:
use std::{
future::Future,
mem,
pin::Pin,
sync::atomic::{
Ordering::{Relaxed, SeqCst},
fence,
},
task::{Context, Poll, Waker},
};
use maillon::{List, Node, NodeData, NodeState, list::LockedList, node_wrapper};
#[derive(Default)]
pub struct WaitList {
list: List<Waiter>,
}
#[derive(Default)]
struct Waiter {
waker: Option<Waker>,
notified: bool,
}
impl WaitList {
pub fn notify_one(&self) {
fence(SeqCst);
if !self.list.is_empty(Relaxed) {
Self::notify_one_cold(&self.list);
}
}
#[cold]
fn notify_one_cold(list: &List<Waiter>) {
Self::notify_one_locked(list.lock());
}
fn notify_one_locked(mut locked: LockedList<'_, Waiter>) {
let Some(mut front) = locked.front() else {
return;
};
front.notified = true;
let waker = front.waker.take();
front.unlink();
drop(locked);
if let Some(waker) = waker {
waker.wake();
}
}
pub fn wait(&self) -> Wait<'_> {
Wait(Node::new(&self.list))
}
}
node_wrapper! {
pub struct Wait<'a>(Node<&'a List<Waiter>>);
}
impl Future for Wait<'_> {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
match self.node_mut().state() {
NodeState::Unlinked(mut node) => {
if mem::take(&mut node.notified) {
return Poll::Ready(());
}
node.waker = Some(cx.waker().clone());
node.push_back(Relaxed);
fence(SeqCst);
Poll::Pending
}
NodeState::Linked(mut node) => node.update_waker(cx, |node| &mut node.waker),
}
}
}
impl NodeData<&List<Waiter>> for Waiter {
fn new_state_if_last_node_on_drop(
self: Pin<&mut Self>,
_list: &&List<Waiter>,
_list_data: &mut (),
) {
}
fn on_drop<'list>(
self: Pin<&mut Self>,
list: &'list &List<Waiter>,
locked: Option<LockedList<'list, Self>>,
_state_updated_on_unlink: bool,
) {
if self.notified {
match locked {
Some(locked) => WaitList::notify_one_locked(locked),
None => WaitList::notify_one_cold(list),
}
}
}
}See the examples for full implementations of tokio::sync::Notify and
tokio::sync::Semaphore built with maillon, with fully identical API and behavior.
Re-exports§
pub use list::List;pub use list::ListRef;pub use list::LockedList;pub use node::Node;pub use node::NodeData;pub use node::NodeState;pub use wait_list::WaitList;pub use atomic_backoff as backoff;
Modules§
- linking
Linkingand its variants.- list
- The
Listand the types to operate on it. - node
- The list
Nodeand its accessors. - sync
- The synchronization primitive abstractions (
MutexandParker) used byList, and their implementations for the supported backends. - wait_
list - An asynchronous wait list with customizable synchronization, built on top of
List.
Macros§
- node_
wrapper - A macro to define a simple wrapper around a
Node, handling pin-projection.
Structs§
- Waker
Batch - A fixed-capacity buffer of
Wakers, stored inline.