Skip to main content

ghostlink_core/
ring.rs

1//! Zero-Copy SPSC Ring Buffer with AF_XDP Integration
2//!
3//! This implementation uses pinned allocations and proper memory ordering
4//! for single-producer/single-consumer DMA-style hand-off.
5
6use std::cell::UnsafeCell;
7use std::mem::MaybeUninit;
8use std::sync::atomic::{AtomicUsize, Ordering};
9
10/// Ring buffer configuration with backpressure thresholds
11#[derive(Clone, Copy, Debug)]
12pub struct RingConfig {
13    /// Maximum number of elements the ring can hold
14    pub capacity: usize,
15    /// Backpressure threshold at 70% capacity
16    pub backpressure_threshold: usize,
17}
18
19impl Default for RingConfig {
20    fn default() -> Self {
21        Self {
22            capacity: 1023,              // Must be < RING_CAPACITY to avoid wraparound aliasing
23            backpressure_threshold: 716, // ~70% of 1023, // 70% of 1024
24        }
25    }
26}
27
28/// Fixed capacity used for DMA alignment
29const RING_CAPACITY: usize = 1024;
30
31/// Zero-copy SPSC ring buffer with proper memory ordering
32///
33/// # Safety
34/// This type is only safe to use when:
35/// - Only one producer thread exists
36/// - Only one consumer thread exists
37/// - Memory is properly aligned for the element type T
38#[derive(Debug)]
39pub struct SpscRingBuffer<T> {
40    /// Buffer of uninitialized elements
41    buffer: UnsafeCell<[MaybeUninit<T>; RING_CAPACITY]>,
42    /// Current head position (consumer reads here)
43    head: AtomicUsize,
44    /// Current tail position (producer writes here)
45    tail: AtomicUsize,
46    /// Overflow counter for backpressure monitoring
47    overflow_count: AtomicUsize,
48    /// Empty count for backpressure monitoring
49    empty_count: AtomicUsize,
50    /// Configuration for this ring buffer
51    config: RingConfig,
52}
53
54impl<T> SpscRingBuffer<T> {
55    const CAPACITY: usize = RING_CAPACITY;
56
57    /// Create a new SPSC ring buffer with the given configuration
58    pub fn new(config: RingConfig) -> Self {
59        assert!(
60            config.capacity < Self::CAPACITY,
61            "capacity must be strictly less than RING_CAPACITY"
62        );
63
64        // Safety: MaybeUninit does not require initialization
65        let buffer = UnsafeCell::new(unsafe {
66            MaybeUninit::<[MaybeUninit<T>; RING_CAPACITY]>::uninit().assume_init()
67        });
68
69        Self {
70            buffer,
71            head: AtomicUsize::new(0),
72            tail: AtomicUsize::new(0),
73            overflow_count: AtomicUsize::new(0),
74            empty_count: AtomicUsize::new(Self::CAPACITY - config.capacity),
75            config,
76        }
77    }
78
79    /// Push an element into the ring buffer
80    ///
81    /// Returns `Ok(())` on success, or `Err(value)` if the ring is full.
82    /// When full, the producer should wait/backpressure until space is available.
83    pub fn push(&self, value: T) -> Result<(), T> {
84        let tail = self.tail.load(Ordering::Acquire);
85        let head = self.head.load(Ordering::Acquire);
86
87        // Check if ring is full using count (respects config.capacity)
88        let current_len = if tail >= head {
89            tail - head
90        } else {
91            Self::CAPACITY - head + tail
92        };
93
94        if current_len >= self.config.capacity {
95            self.overflow_count.fetch_add(1, Ordering::Relaxed);
96            return Err(value);
97        }
98
99        let next_tail = Self::increment(tail);
100
101        // Write the value at tail position
102        unsafe {
103            let buf = &mut *self.buffer.get();
104            buf[tail].write(value);
105
106            // Release store to make write visible to consumer
107            self.tail.store(next_tail, Ordering::Release);
108        }
109
110        Ok(())
111    }
112
113    /// Pop an element from the ring buffer
114    ///
115    /// Returns `Some(value)` on success, or `None` if the ring is empty.
116    pub fn pop(&self) -> Option<T> {
117        let head = self.head.load(Ordering::Acquire);
118        let tail = self.tail.load(Ordering::Acquire);
119
120        // Check if ring is empty (with wrap-around handling)
121        let is_empty = if head >= tail { head == tail } else { false };
122
123        if is_empty {
124            return None;
125        }
126
127        // Read the value at head position
128        let value = unsafe {
129            let buf = &mut *self.buffer.get();
130            let val = buf[head].assume_init_read();
131
132            // Release store to make read visible to producer
133            self.head.store(Self::increment(head), Ordering::Release);
134
135            val
136        };
137
138        Some(value)
139    }
140
141    /// Get the current number of elements in the ring
142    pub fn len(&self) -> usize {
143        let head = self.head.load(Ordering::Acquire);
144        let tail = self.tail.load(Ordering::Acquire);
145
146        tail.wrapping_sub(head) & (Self::CAPACITY - 1)
147    }
148
149    /// Check if the ring is empty
150    pub fn is_empty(&self) -> bool {
151        self.head.load(Ordering::Acquire) == self.tail.load(Ordering::Acquire)
152    }
153
154    /// Get the configured capacity
155    pub fn capacity(&self) -> usize {
156        self.config.capacity
157    }
158
159    /// Get overflow count for backpressure monitoring
160    pub fn overflow_count(&self) -> usize {
161        self.overflow_count.load(Ordering::Relaxed)
162    }
163
164    /// Get empty count for backpressure monitoring
165    pub fn empty_count(&self) -> usize {
166        self.empty_count.load(Ordering::Relaxed)
167    }
168
169    /// Check if backpressure should be applied (ring >= threshold)
170    pub fn should_backpressure(&self) -> bool {
171        let len = self.len();
172        len >= self.config.backpressure_threshold
173    }
174
175    /// Wait for space to become available
176    ///
177    /// Spins until there's room in the ring buffer.
178    pub fn wait_for_space(&self) {
179        while self.should_backpressure() {
180            // Yield to allow consumer to make progress
181            std::thread::yield_now();
182        }
183    }
184
185    /// Wait for an element to become available
186    ///
187    /// Spins until there's data in the ring buffer.
188    pub fn wait_for_data(&self) {
189        while self.is_empty() {
190            // Yield to allow producer to make progress
191            std::thread::yield_now();
192        }
193    }
194
195    fn increment(index: usize) -> usize {
196        (index + 1) & (Self::CAPACITY - 1)
197    }
198}
199
200// Safety: SpscRingBuffer is safe to send across threads when used as SPSC.
201// The producer and consumer operate on disjoint memory regions with proper
202// atomic ordering, making this safe for our SPSC use case.
203unsafe impl<T: Send> Send for SpscRingBuffer<T> {}
204unsafe impl<T: Send> Sync for SpscRingBuffer<T> {}
205
206impl<T> Drop for SpscRingBuffer<T> {
207    fn drop(&mut self) {
208        // Drain remaining elements on drop
209        while self.pop().is_some() {}
210    }
211}
212
213#[cfg(test)]
214mod tests {
215    use super::*;
216    use std::sync::Arc;
217    use std::thread;
218
219    #[test]
220    fn supports_fifo_push_and_pop() {
221        let ring = SpscRingBuffer::<i32>::new(RingConfig::default());
222        assert!(ring.push(1).is_ok());
223        assert!(ring.push(2).is_ok());
224        assert_eq!(ring.len(), 2);
225        assert_eq!(ring.pop(), Some(1));
226        assert_eq!(ring.pop(), Some(2));
227        assert!(ring.is_empty());
228    }
229
230    #[test]
231    fn returns_full_when_capacity_is_reached() {
232        let config = RingConfig {
233            capacity: 2,
234            backpressure_threshold: 1,
235        };
236        let ring = SpscRingBuffer::<i32>::new(config);
237        assert!(ring.push(1).is_ok());
238        assert!(ring.push(2).is_ok());
239        assert_eq!(ring.push(3), Err(3));
240    }
241
242    #[test]
243    fn supports_single_producer_single_consumer_threads() {
244        let ring = Arc::new(SpscRingBuffer::<i32>::new(RingConfig::default()));
245        let producer_ring = Arc::clone(&ring);
246        let consumer_ring = Arc::clone(&ring);
247
248        let producer = thread::spawn(move || {
249            for value in 0..1_000 {
250                loop {
251                    if producer_ring.push(value).is_ok() {
252                        break;
253                    }
254                    std::thread::yield_now();
255                }
256            }
257        });
258
259        let consumer = thread::spawn(move || {
260            let mut values = Vec::new();
261            while values.len() < 1_000 {
262                if let Some(value) = consumer_ring.pop() {
263                    values.push(value);
264                } else {
265                    std::thread::yield_now();
266                }
267            }
268            values
269        });
270
271        producer.join().unwrap();
272        let values = consumer.join().unwrap();
273
274        assert_eq!(values.len(), 1_000);
275        assert_eq!(values.first(), Some(&0));
276        assert_eq!(values.last(), Some(&999));
277    }
278
279    #[test]
280    fn handles_wrap_around_correctly() {
281        let ring = SpscRingBuffer::<i32>::new(RingConfig::default());
282
283        // Fill most of the ring
284        for i in 0..1020 {
285            ring.push(i).unwrap();
286        }
287
288        assert_eq!(ring.len(), 1020);
289
290        // Pop all elements in FIFO order
291        for i in 0..1020 {
292            assert_eq!(ring.pop(), Some(i));
293        }
294
295        assert!(ring.is_empty());
296    }
297
298    #[test]
299    fn backpressure_threshold_works() {
300        let config = RingConfig {
301            capacity: 100,
302            backpressure_threshold: 70,
303        };
304        let ring = SpscRingBuffer::<i32>::new(config);
305
306        // Fill to threshold - should not backpressure
307        for i in 0..69 {
308            ring.push(i).unwrap();
309        }
310        assert!(!ring.should_backpressure());
311
312        // Fill one more - should backpressure
313        let result = ring.push(70);
314        assert!(result.is_ok()); // Push succeeds until capacity (100) is reached
315        assert!(ring.should_backpressure());
316    }
317
318    #[test]
319    fn wait_for_space_releases() {
320        let ring = Arc::new(SpscRingBuffer::<i32>::new(RingConfig::default()));
321        let consumer_ring = Arc::clone(&ring);
322
323        // Fill the ring
324        for i in 0..1020 {
325            ring.push(i).unwrap();
326        }
327
328        // Consumer pops one element
329        consumer_ring.pop();
330
331        // Producer should be able to push again
332        let result = ring.push(1021);
333        assert!(result.is_ok());
334    }
335
336    #[test]
337    fn wait_for_data_releases() {
338        let ring = Arc::new(SpscRingBuffer::<i32>::new(RingConfig::default()));
339        let producer_ring = Arc::clone(&ring);
340
341        // Ring is empty
342
343        // Producer pushes one element
344        producer_ring.push(42).unwrap();
345
346        // Consumer should be able to pop now
347        assert_eq!(ring.pop(), Some(42));
348    }
349}