simoom_sys/mpsc.rs
1//! A lock-free multi-producer, single-consumer queue: any thread pushes, one thread takes
2//! everything at once.
3//!
4//! Producers push onto an atomic stack with one compare-and-swap; the consumer swaps the whole
5//! stack out in one atomic exchange and reverses it, so items come out in the order they were
6//! pushed (per producer, and in push order overall). There is no lock, so a producer never
7//! waits on the consumer or on another producer's critical section, and nothing ever calls into
8//! the kernel. Taking the whole stack at once has no ABA problem: a node is only ever freed by
9//! the consumer after it left the shared stack.
10//!
11//! The event loops use it for messages from other threads (`Responder::send`, offloaded jobs),
12//! together with [`crate::driver::Waker::post`], which wakes a loop only when it is parked.
13
14use core::ptr;
15use std::marker::PhantomData;
16use std::sync::atomic::{AtomicPtr, Ordering};
17
18struct Node<T> {
19 next: *mut Node<T>,
20 value: T,
21}
22
23/// The queue (see the module documentation).
24pub struct Mpsc<T> {
25 head: AtomicPtr<Node<T>>,
26 _owns: PhantomData<Box<Node<T>>>,
27}
28
29// SAFETY: the queue moves `T` values between threads (so `T: Send`), and every access to the
30// shared head is atomic; nodes are owned by exactly one side at a time (a producer until its
31// compare-and-swap succeeds, then the stack, then the consumer after its exchange).
32unsafe impl<T: Send> Send for Mpsc<T> {}
33// SAFETY: as above; `push` and `take` only touch the head through atomics.
34unsafe impl<T: Send> Sync for Mpsc<T> {}
35
36impl<T> Default for Mpsc<T> {
37 fn default() -> Self {
38 Mpsc::new()
39 }
40}
41
42impl<T> std::fmt::Debug for Mpsc<T> {
43 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 f.debug_struct("Mpsc").field("empty", &self.is_empty()).finish()
45 }
46}
47
48impl<T> Mpsc<T> {
49 /// An empty queue.
50 pub const fn new() -> Mpsc<T> {
51 Mpsc { head: AtomicPtr::new(ptr::null_mut()), _owns: PhantomData }
52 }
53
54 /// Adds `value` (any thread). Sequentially consistent, so a consumer that announces it is
55 /// about to sleep and then checks [`Mpsc::is_empty`] either sees the value or is seen by
56 /// the producer's following check of that announcement.
57 pub fn push(&self, value: T) {
58 let node = Box::into_raw(Box::new(Node { next: ptr::null_mut(), value }));
59 let mut head = self.head.load(Ordering::Relaxed);
60 loop {
61 // SAFETY: `node` came from `Box::into_raw` above and is not shared until the
62 // compare-and-swap below succeeds, so this thread has exclusive access to it.
63 unsafe { (*node).next = head };
64 match self.head.compare_exchange_weak(head, node, Ordering::SeqCst, Ordering::Relaxed) {
65 Ok(_) => return,
66 Err(h) => head = h,
67 }
68 }
69 }
70
71 /// Whether the queue looks empty (a push may be landing concurrently).
72 #[inline]
73 pub fn is_empty(&self) -> bool {
74 self.head.load(Ordering::SeqCst).is_null()
75 }
76
77 /// Moves every queued value into `into`, oldest first (the consumer thread).
78 pub fn take(&self, into: &mut Vec<T>) {
79 if self.head.load(Ordering::Relaxed).is_null() {
80 return;
81 }
82 let mut p = self.head.swap(ptr::null_mut(), Ordering::Acquire);
83 let start = into.len();
84 while !p.is_null() {
85 // SAFETY: the exchange above took the whole list out of the shared head, so no
86 // other thread can reach these nodes any more; each was made by `Box::into_raw` in
87 // `push` and is freed exactly once here.
88 let node = unsafe { Box::from_raw(p) };
89 p = node.next;
90 into.push(node.value);
91 }
92 // The stack holds the newest first.
93 into[start..].reverse();
94 }
95}
96
97impl<T> Drop for Mpsc<T> {
98 fn drop(&mut self) {
99 let mut p = *self.head.get_mut();
100 while !p.is_null() {
101 // SAFETY: `&mut self` means no producer or consumer is running; every node in the
102 // list came from `Box::into_raw` and is freed exactly once.
103 let node = unsafe { Box::from_raw(p) };
104 p = node.next;
105 }
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use std::sync::Arc;
113
114 #[test]
115 fn values_come_out_in_push_order() {
116 let q = Mpsc::new();
117 let mut v = Vec::new();
118 q.take(&mut v);
119 assert!(v.is_empty() && q.is_empty());
120 for i in 0..5 {
121 q.push(i);
122 }
123 assert!(!q.is_empty());
124 v.push(-1);
125 q.take(&mut v);
126 assert_eq!(v, [-1, 0, 1, 2, 3, 4]);
127 assert!(q.is_empty());
128 }
129
130 #[test]
131 fn many_producers_lose_nothing_and_keep_their_own_order() {
132 let q = Arc::new(Mpsc::new());
133 let producers: Vec<_> = (0..8u64)
134 .map(|p| {
135 let q = q.clone();
136 std::thread::spawn(move || {
137 for i in 0..20_000u64 {
138 q.push((p, i));
139 }
140 })
141 })
142 .collect();
143 let mut seen = vec![0u64; 8];
144 let mut got = 0;
145 let mut buf = Vec::new();
146 while got < 8 * 20_000 {
147 q.take(&mut buf);
148 for (p, i) in buf.drain(..) {
149 assert_eq!(seen[p as usize], i, "producer {p} out of order");
150 seen[p as usize] += 1;
151 got += 1;
152 }
153 }
154 for t in producers {
155 t.join().unwrap();
156 }
157 assert!(q.is_empty());
158 }
159
160 #[test]
161 fn dropping_a_full_queue_drops_its_values() {
162 let marker = Arc::new(());
163 let q = Mpsc::new();
164 for _ in 0..10 {
165 q.push(marker.clone());
166 }
167 assert_eq!(Arc::strong_count(&marker), 11);
168 drop(q);
169 assert_eq!(Arc::strong_count(&marker), 1);
170 }
171}