Skip to main content

llt_rs/ring_buffer/
mod.rs

1#![doc = include_str!("README.md")]
2
3use crossbeam_utils::CachePadded;
4use std::cell::UnsafeCell;
5use std::mem::MaybeUninit;
6use std::sync::atomic::{AtomicUsize, Ordering};
7
8/// A Single-Producer, Single-Consumer (SPSC) lock free ring buffer.
9/// This queue is "wait-free" (bounded time) for both producer and consumer.
10/// It does not block, but return `Err` or `None` if the queue is full or empty.
11pub struct RingBuffer<T> {
12    ///The buffer, allocated on the heap
13    /// We use `UnsafeCell` for interior mutability (to write from `&self`).
14    /// We use `MaybeUninit` to store uninitialized data and take ownership
15    /// of `T's` when we `recv`
16    buffer: Box<[UnsafeCell<MaybeUninit<T>>]>,
17
18    /// The capacity of the buffer, Must be a power of 2 (an optimization that allows us to use bit trick instead of modulo)
19    cap: usize,
20
21    /// The `head` counter.
22    /// This is where the producer will write the *next* item.
23    /// Only the producer modifies this.
24    /// Padded to prevent false sharing with `tail`.
25    head: CachePadded<AtomicUsize>,
26
27    ///The "tail" counter
28    /// This is where the consumer will read the *next* item from.
29    /// Only the consumer modifies this
30    /// Padded to prevent false sharing with `head`.
31    tail: CachePadded<AtomicUsize>,
32}
33
34/// We can safely send the RingBuffer to other threads if T is Send
35/// `Unsafe` is not `Sync` BUT WE *know* we are only accessing
36/// the buffer safely from the *single* producer and *single* consumer.
37/// The `head` and `tail` atomics prevent reading/writing the same slot.
38unsafe impl<T: Send> Sync for RingBuffer<T> {}
39unsafe impl<T: Send> Send for RingBuffer<T> {}
40
41impl<T> RingBuffer<T> {
42    /// Creates a new SPSC ring buffer with *at least* the given capacity
43    /// The actual capacity will be rounded up to the next power of 2.
44    pub fn new(capacity: usize) -> Self {
45        // Round up to the next power of 2
46        //this allows us to replace a slow modulo with fast BITWISE-AND
47        let cap = capacity.next_power_of_two();
48        //Create a Vec and fill it with uninitialized data
49        let mut buffer = Vec::with_capacity(cap);
50        for _ in 0..cap {
51            buffer.push(UnsafeCell::new(MaybeUninit::uninit()));
52        }
53
54        // Convert the Vec to a Box<[]>
55        let buffer = buffer.into_boxed_slice();
56
57        Self {
58            buffer,
59            cap,
60            head: CachePadded::new(AtomicUsize::new(0)),
61            tail: CachePadded::new(AtomicUsize::new(0)),
62        }
63    }
64
65    /// Returns the capacity of the ring buffer.
66    pub fn capacity(&self) -> usize {
67        self.cap
68    }
69
70    ///Returns the number of items currently in the buffer.
71    /// This is a snapshot and maybe out of date immediately.
72    pub fn len(&self) -> usize {
73        let head = self.head.load(Ordering::Relaxed);
74        let tail = self.tail.load(Ordering::Relaxed);
75        head.wrapping_sub(tail)
76    }
77    ///Returns true if the buffer is empty.
78    pub fn is_empty(&self) -> bool {
79        self.len() == 0
80    }
81
82    /// sends a item into a buffer
83    ///
84    /// Fails if the buffer is full, returning an `Err(item)`.
85    /// This is the *Producer* method.
86    pub fn send(&self, item: T) -> Result<(), T> {
87        // Load the current head and tail.
88        // `head` can be Relaxed because only *we* can change it.
89        // `tail` must be `Acquire` to "see" the consumer's `Release`
90        // store, which signals that a slot has been freed.
91        let head = self.head.load(Ordering::Relaxed);
92        let tail = self.tail.load(Ordering::Acquire);
93        //Check if the buffer is full
94        // `wrapping_sub` handles counter wrap-around.
95        if head.wrapping_sub(tail) == self.cap {
96            return Err(item);
97        }
98        // Calculate the slot index using the power-of-2 bit-trick.
99        // This is much faster than `head % self.cap`.
100        let slot_idx = head & (self.cap - 1);
101
102        // SAFETY:
103        // 1. `&self` is_ok because  `UnsafeCell` provides interior mutability.
104        // 2. The `is_full` check (head - tail == cap) guarantees that
105        // this slot is "owned" by the producer. The consumer will
106        // *never* read from this slot until we increment `head`.
107        // 3. We are writing a `MaybeUninit::new(item)` which is valid.
108        unsafe {
109            let slot_ptr = self.buffer[slot_idx].get();
110            (*slot_ptr).write(item);
111        }
112        // "Publish" the write.
113        // We use `Release` to ensure that the data write (above)
114        // is *not* reordered *after* this store. This makes the
115        // data visible to the consumer's `Acquire` load.
116        self.head.store(head.wrapping_add(1), Ordering::Release);
117        Ok(())
118    }
119    pub fn recv(&self) -> Option<T> {
120        //Load the current head and tail.
121        // `tail` can be Relaxed because only *we* change it.
122        // `head` must be `Acquire` to "see" the producer's `Release`
123        // store, which signals that data is available.
124        let tail = self.tail.load(Ordering::Relaxed);
125        let head = self.head.load(Ordering::Acquire);
126
127        //Check if the buffer is empty
128        if tail == head {
129            return None;
130        }
131
132        let slot_idx = tail & (self.cap - 1);
133        //Calculate the slot index.
134        //SAFETY.
135        //1. `&self` is ok because `UnsafeCell`.
136        //2. The `is_empty` check (head == tail) guarantees that
137        // this slot *contains data* put there by the producer.
138        //3. The producer's `Release` store (on `head`) ensures that
139        // the data write is visible *before*  we saw the new `head`.
140        // 4 . `assume_init_read` takes ownership of the `T`, leaving
141        // The `MaybeUninit` in a "uninitialized" state which is fine
142
143        let item = unsafe {
144            let slot_ptr = self.buffer[slot_idx].get();
145            (*slot_ptr).assume_init_read()
146        };
147        // "Publish" that we have a freed up a slot.
148        // We use `Release` to ensure that our "take" (the read)
149        // is visible to the producer's `Acquire` load of `tail`.
150
151        self.tail.store(tail.wrapping_add(1), Ordering::Release);
152
153        Some(item)
154    }
155}
156
157/// We must implement Drop to clean up any `T` a left in the buffer.
158impl<T> Drop for RingBuffer<T> {
159    fn drop(&mut self) {
160        //We are in `&MUT self`, so no other threads can be accessing
161        // the buffer, We can use `Relaxed` ordering;
162        let mut tail = self.tail.load(Ordering::Relaxed);
163        let head = self.head.load(Ordering::Relaxed);
164        while tail != head {
165            let slot_idx = tail & (self.cap - 1);
166            //SAFETY:
167            //1. We have `&mut self`, so no other thread is racing.
168            //2. We are iterating from `tail` to `head` which are the
169            // slots that contain initialized data.
170            // 3 `drop_in_place` calls the destructor for `T`
171            unsafe {
172                let slot_ptr = self.buffer[slot_idx].get();
173                //Use `as_mut` to get `&mut MaybeUninit<T>`
174                //and then `drop_in_place` on its contents.
175                std::ptr::drop_in_place((*slot_ptr).as_mut_ptr());
176            }
177            tail = tail.wrapping_add(1);
178        }
179    }
180}
181#[cfg(test)]
182mod tests {
183    use super::*;
184    use std::sync::Arc;
185    use std::thread;
186
187    #[test]
188    fn test_single_thread_send_recv() {
189        let rb = RingBuffer::new(4);
190        assert_eq!(rb.capacity(), 4); // 4 is a power of 2
191
192        rb.send("hello").unwrap();
193        rb.send("world").unwrap();
194
195        assert_eq!(rb.len(), 2);
196
197        assert_eq!(rb.recv(), Some("hello"));
198        assert_eq!(rb.recv(), Some("world"));
199        assert_eq!(rb.recv(), None);
200        assert_eq!(rb.len(), 0);
201    }
202
203    #[test]
204    fn test_full_and_empty() {
205        let rb = RingBuffer::new(2);
206        assert_eq!(rb.capacity(), 2);
207
208        rb.send(1).unwrap();
209        rb.send(2).unwrap();
210
211        // Buffer is full
212        assert_eq!(rb.send(3), Err(3));
213        assert_eq!(rb.len(), 2);
214
215        // Receive one
216        assert_eq!(rb.recv(), Some(1));
217        assert_eq!(rb.len(), 1);
218
219        // Now we can send again
220        rb.send(3).unwrap();
221        assert_eq!(rb.len(), 2);
222
223        assert_eq!(rb.recv(), Some(2));
224        assert_eq!(rb.recv(), Some(3));
225        assert_eq!(rb.recv(), None);
226        assert_eq!(rb.len(), 0);
227    }
228
229    #[test]
230    fn test_multi_thread_spsc() {
231        // Use Arc to share the RingBuffer between threads
232        let rb = Arc::new(RingBuffer::new(1024));
233        let num_items = 1_000_000;
234
235        let producer_rb = rb.clone();
236        let producer_thread = thread::spawn(move || {
237            for i in 0..num_items {
238                // Spin-wait if the buffer is full
239                // FIX: Use `_item` to mark the variable as intentionally unused.
240                while let Err(_item) = producer_rb.send(i) {
241                    thread::yield_now();
242                }
243            }
244        });
245
246        let consumer_rb = rb.clone();
247        let consumer_thread = thread::spawn(move || {
248            let mut received_count = 0;
249            let mut next_expected = 0;
250            while received_count < num_items {
251                // Spin-wait if the buffer is empty
252                match consumer_rb.recv() {
253                    Some(item) => {
254                        assert_eq!(item, next_expected);
255                        next_expected += 1;
256                        received_count += 1;
257                    }
258                    None => {
259                        thread::yield_now();
260                    }
261                }
262            }
263        });
264
265        producer_thread.join().unwrap();
266        consumer_thread.join().unwrap();
267    }
268
269    #[test]
270    fn test_drop_cleanup() {
271        // A simple type to track drops
272        static DROP_COUNT: AtomicUsize = AtomicUsize::new(0);
273        // FIX: Add `#[derive(Debug)]` so that `unwrap()` on `Result` can compile.
274        #[derive(Debug)]
275        struct Dropper;
276        impl Drop for Dropper {
277            fn drop(&mut self) {
278                DROP_COUNT.fetch_add(1, Ordering::SeqCst);
279            }
280        }
281
282        DROP_COUNT.store(0, Ordering::SeqCst);
283
284        // Create a new scope
285        {
286            let rb = RingBuffer::new(8);
287            rb.send(Dropper).unwrap();
288            rb.send(Dropper).unwrap();
289            rb.send(Dropper).unwrap();
290
291            // Receive one
292            // FIX: Introduce a new scope to force `_d` to be
293            // dropped immediately after it's received.
294            {
295                let _d = rb.recv().unwrap();
296            } // _d (and the Dropper it holds) is dropped right here.
297
298            // at this point, 1 should be dropped
299            assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 1);
300
301            // two items are still in the buffer
302            // when `rb` goes out of scope, it should be dropped
303        }
304
305        // The Drop implementation of RingBuffer should have
306        // dropped the remaining 2 items.
307        assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 3);
308    }
309}