Skip to main content

cloudpub_common/
fair_channel.rs

1extern crate alloc;
2
3use alloc::collections::VecDeque;
4use alloc::vec::Vec;
5use parking_lot::RwLock;
6use std::sync::Arc;
7use tokio::sync::mpsc::error::{SendError, TryRecvError, TrySendError};
8use tokio::sync::Notify;
9
10/// Trait for defining grouping logic for fair queuing.
11/// Groups are determined based on `group_id`. None represents the control group.
12pub trait FairGroup: Clone {
13    fn group_id(&self) -> Option<u32>;
14    fn get_size(&self) -> Option<usize>;
15}
16
17/// Spatially distancing fair queue. First in, first out, ensuring that
18/// each group of similar values is placed as far apart as possible.
19pub struct FairQueue<V: FairGroup> {
20    ctrl_group: VecDeque<Arc<V>>,
21    groups: Vec<VecDeque<Arc<V>>>,
22    pointer: usize,
23    max_group_size: usize,
24}
25
26impl<V: FairGroup> FairQueue<V> {
27    pub fn new(max_group_size: usize) -> Self {
28        Self {
29            ctrl_group: VecDeque::new(),
30            groups: Vec::new(),
31            pointer: 0,
32            max_group_size,
33        }
34    }
35
36    /// Check if a value can be inserted without exceeding group limits
37    pub fn can_insert(&self, value: &V) -> bool {
38        match value.group_id() {
39            None => true, // Control group can always be inserted
40            Some(group_id) => {
41                if let Some(group) = self
42                    .groups
43                    .iter()
44                    .find(|group| group.front().map(|v| v.group_id()) == Some(Some(group_id)))
45                {
46                    let can = group.len() < self.max_group_size;
47                    if !can {
48                        // Not an error: the sender waits for room, and it
49                        // re-checks after every notify wake-up, so this fires
50                        // once per wake-up for as long as the group is full
51                        // (CLOUDPUB-1509).
52                        tracing::debug!(
53                            group_id = %group_id,
54                            len = group.len(),
55                            max = self.max_group_size,
56                            "fair channel group is full, sender waits"
57                        );
58                    }
59                    can
60                } else {
61                    true // New group can always be created
62                }
63            }
64        }
65    }
66
67    /// Inserts a new item into the queue, ensuring spatial distancing between items of the same group.
68    /// Returns true if inserted successfully, false if group is full.
69    pub fn insert(&mut self, value: Arc<V>) -> bool {
70        //value.trace_message("INSERT");
71        match value.group_id() {
72            None => {
73                // Control group (group_id is None)
74                self.ctrl_group.push_back(value);
75                true
76            }
77            Some(group_id) => {
78                // Regular group
79                if let Some(group) = self
80                    .groups
81                    .iter_mut()
82                    .find(|group| group.front().map(|v| v.group_id()) == Some(Some(group_id)))
83                {
84                    if group.len() >= self.max_group_size {
85                        return false; // Group is full
86                    }
87                    group.push_back(value);
88                } else {
89                    let mut new_group = VecDeque::new();
90                    new_group.push_back(value);
91                    self.groups.push(new_group);
92                }
93                true
94            }
95        }
96    }
97
98    #[inline(always)]
99    pub fn pop(&mut self) -> Option<Arc<V>> {
100        if let Some(v) = self.ctrl_group.pop_front() {
101            //v.trace_message("POP");
102            return Some(v);
103        }
104        for _ in 0..self.groups.len() {
105            let pointer = self.pointer;
106            // Optimistically move queue pointer to the next group
107            self.pointer = (pointer + 1) % self.groups.len();
108
109            let group = &mut self.groups[pointer];
110            let item = group.pop_front();
111
112            if item.is_some() {
113                if group.is_empty() {
114                    self.groups.remove(pointer);
115                    if pointer < self.groups.len() {
116                        self.pointer = pointer;
117                    } else {
118                        self.pointer = 0;
119                    }
120                }
121                return item;
122            }
123        }
124
125        None
126    }
127}
128
129/// Shared state between sender and receiver
130struct ChannelState<T: FairGroup + 'static> {
131    queue: FairQueue<T>,
132    closed: bool,
133}
134
135impl<T: FairGroup + 'static> ChannelState<T> {
136    fn new(max_group_size: usize) -> Self {
137        Self {
138            queue: FairQueue::new(max_group_size),
139            closed: false,
140        }
141    }
142
143    fn can_insert(&self, value: &T) -> bool {
144        self.queue.can_insert(value)
145    }
146}
147
148/// Sender half of the fair channel
149pub struct FairSender<T: FairGroup + 'static> {
150    state: Arc<RwLock<ChannelState<T>>>,
151    notify_recv: Arc<Notify>,
152    notify_send: Arc<Notify>,
153}
154
155impl<T: FairGroup + 'static> Clone for FairSender<T> {
156    fn clone(&self) -> Self {
157        Self {
158            state: Arc::clone(&self.state),
159            notify_recv: Arc::clone(&self.notify_recv),
160            notify_send: Arc::clone(&self.notify_send),
161        }
162    }
163}
164
165impl<T: FairGroup + 'static> FairSender<T> {
166    /// Send a value, waiting if the channel is full
167    pub async fn send(&self, value: T) -> Result<(), SendError<T>> {
168        let value_arc = Arc::new(value);
169
170        loop {
171            {
172                let mut state = self.state.write();
173                if state.closed {
174                    return Err(SendError(
175                        Arc::try_unwrap(value_arc).unwrap_or_else(|arc| (*arc).clone()),
176                    ));
177                }
178
179                // Check group capacity
180                if state.can_insert(&value_arc) {
181                    state.queue.insert(value_arc);
182                    drop(state);
183                    self.notify_recv.notify_waiters();
184                    return Ok(());
185                }
186            }
187
188            // Wait for space to become available in the group
189            self.notify_send.notified().await;
190        }
191    }
192
193    /// Try to send a value without waiting
194    pub fn try_send(&self, value: T) -> Result<(), TrySendError<T>> {
195        let value_arc = Arc::new(value);
196
197        let mut state = self.state.write();
198        if state.closed {
199            return Err(TrySendError::Closed(
200                Arc::try_unwrap(value_arc).unwrap_or_else(|arc| (*arc).clone()),
201            ));
202        }
203
204        if !state.queue.can_insert(&value_arc) {
205            return Err(TrySendError::Full(
206                Arc::try_unwrap(value_arc).unwrap_or_else(|arc| (*arc).clone()),
207            ));
208        }
209
210        state.queue.insert(value_arc);
211        drop(state); // Release lock before notifying
212        self.notify_recv.notify_waiters();
213        Ok(())
214    }
215
216    /// Check if the channel is closed
217    pub async fn closed(&self) {
218        loop {
219            {
220                let state = self.state.read();
221                if state.closed {
222                    return;
223                }
224            }
225
226            // Wait for the channel to be closed
227            self.notify_send.notified().await;
228        }
229    }
230}
231
232/// Receiver half of the fair channel
233pub struct FairReceiver<T: FairGroup + 'static> {
234    state: Arc<RwLock<ChannelState<T>>>,
235    notify_recv: Arc<Notify>,
236    notify_send: Arc<Notify>,
237}
238
239impl<T: FairGroup + 'static> FairReceiver<T> {
240    /// Receive a value, waiting if the channel is empty
241    pub async fn recv(&mut self) -> Option<T> {
242        loop {
243            {
244                let mut state = self.state.write();
245                if let Some(value_arc) = state.queue.pop() {
246                    drop(state);
247                    self.notify_send.notify_waiters();
248                    return Some(Arc::try_unwrap(value_arc).unwrap_or_else(|arc| (*arc).clone()));
249                }
250
251                if state.closed {
252                    return None;
253                }
254            }
255
256            // Wait for a value to become available
257            self.notify_recv.notified().await;
258        }
259    }
260
261    /// Try to receive a value without waiting
262    pub fn try_recv(&mut self) -> Result<T, TryRecvError> {
263        let mut state = self.state.write();
264
265        if let Some(value_arc) = state.queue.pop() {
266            drop(state); // Release lock before notifying
267            self.notify_send.notify_waiters();
268            return Ok(Arc::try_unwrap(value_arc).unwrap_or_else(|arc| (*arc).clone()));
269        }
270
271        if state.closed {
272            Err(TryRecvError::Disconnected)
273        } else {
274            Err(TryRecvError::Empty)
275        }
276    }
277
278    /// Close the receiver, which will cause all senders to return errors
279    pub async fn close(&mut self) {
280        let mut state = self.state.write();
281        state.closed = true;
282        drop(state); // Release lock before notifying
283        self.notify_send.notify_waiters();
284    }
285}
286
287impl<T: FairGroup + 'static> Drop for FairReceiver<T> {
288    fn drop(&mut self) {
289        // Mark the channel as closed when receiver is dropped
290        if let Some(mut state) = self.state.try_write() {
291            state.closed = true;
292            drop(state); // Release lock before notifying
293            self.notify_send.notify_waiters();
294        }
295    }
296}
297
298/// Creates a new fair channel with the specified max group size
299pub fn fair_channel<T: FairGroup + 'static>(
300    max_group_size: usize,
301) -> (FairSender<T>, FairReceiver<T>) {
302    let state = Arc::new(RwLock::new(ChannelState::new(max_group_size)));
303    let notify_recv = Arc::new(Notify::new());
304    let notify_send = Arc::new(Notify::new());
305
306    let sender = FairSender {
307        state: Arc::clone(&state),
308        notify_recv: Arc::clone(&notify_recv),
309        notify_send: Arc::clone(&notify_send),
310    };
311
312    let receiver = FairReceiver {
313        state,
314        notify_recv,
315        notify_send,
316    };
317
318    (sender, receiver)
319}
320
321#[cfg(test)]
322mod tests {
323    use super::*;
324    use std::sync::Mutex;
325    use tracing::subscriber::with_default;
326    use tracing_subscriber::layer::{Context, Layer};
327    use tracing_subscriber::prelude::*;
328    use tracing_subscriber::registry::LookupSpan;
329
330    #[derive(Debug, PartialEq, Clone)]
331    struct Event {
332        timestamp: u32,
333        user_id: &'static str,
334    }
335
336    impl FairGroup for Event {
337        fn group_id(&self) -> Option<u32> {
338            // Convert user_id to a hash or use a simple mapping
339            match self.user_id {
340                "user1" => Some(1),
341                "user2" => Some(2),
342                "user3" => Some(3),
343                _ => Some(0), // Default group
344            }
345        }
346
347        fn get_size(&self) -> Option<usize> {
348            None // Not used in current implementation
349        }
350    }
351
352    #[test]
353    fn test_spaced_fairness() {
354        let event1 = Event {
355            timestamp: 1,
356            user_id: "user1",
357        };
358        let event2 = Event {
359            timestamp: 2,
360            user_id: "user2",
361        };
362        let event3 = Event {
363            timestamp: 3,
364            user_id: "user1",
365        };
366        let event4 = Event {
367            timestamp: 4,
368            user_id: "user3",
369        };
370        let event5 = Event {
371            timestamp: 5,
372            user_id: "user2",
373        };
374        let event6 = Event {
375            timestamp: 6,
376            user_id: "user1",
377        };
378        let event7 = Event {
379            timestamp: 7,
380            user_id: "user1",
381        };
382        let event8 = Event {
383            timestamp: 8,
384            user_id: "user3",
385        };
386
387        let mut queue = FairQueue::new(usize::MAX);
388
389        let event1_arc = Arc::new(event1.clone());
390        let event2_arc = Arc::new(event2.clone());
391        let event3_arc = Arc::new(event3.clone());
392        let event4_arc = Arc::new(event4.clone());
393        let event5_arc = Arc::new(event5.clone());
394        let event6_arc = Arc::new(event6.clone());
395        let event7_arc = Arc::new(event7.clone());
396        let event8_arc = Arc::new(event8.clone());
397
398        queue.insert(event1_arc.clone());
399        queue.insert(event2_arc.clone());
400        queue.insert(event3_arc.clone());
401        queue.insert(event4_arc.clone());
402        queue.insert(event5_arc.clone());
403        queue.insert(event6_arc.clone());
404        queue.insert(event7_arc.clone());
405        queue.insert(event8_arc.clone());
406
407        // With weighted round-robin prioritizing smaller buffers:
408        // After insertion: user1=[1,3,6,7], user2=[2,5], user3=[4,8]
409        // Weighted selection will favor smaller groups (user2 and user3)
410        let mut results = Vec::new();
411        while let Some(event) = queue.pop() {
412            results.push(event);
413        }
414
415        // Verify we got all events
416        assert_eq!(results.len(), 8);
417
418        // Verify fairness: smaller groups should be prioritized
419        // The exact order may vary but should favor smaller buffers
420        let user1_events: Vec<_> = results.iter().filter(|e| e.user_id == "user1").collect();
421        let user2_events: Vec<_> = results.iter().filter(|e| e.user_id == "user2").collect();
422        let user3_events: Vec<_> = results.iter().filter(|e| e.user_id == "user3").collect();
423
424        assert_eq!(user1_events.len(), 4);
425        assert_eq!(user2_events.len(), 2);
426        assert_eq!(user3_events.len(), 2);
427    }
428
429    #[tokio::test]
430    async fn test_fair_channel_basic() {
431        let (tx, mut rx) = fair_channel(5);
432
433        let event1 = Event {
434            timestamp: 1,
435            user_id: "user1",
436        };
437        let event2 = Event {
438            timestamp: 2,
439            user_id: "user2",
440        };
441
442        tx.send(event1).await.unwrap();
443        tx.send(event2).await.unwrap();
444
445        let received1 = rx.recv().await.unwrap();
446        let received2 = rx.recv().await.unwrap();
447
448        assert_eq!(received1.timestamp, 1);
449        assert_eq!(received2.timestamp, 2);
450    }
451
452    #[tokio::test]
453    async fn test_fair_channel_fairness() {
454        let (tx, mut rx) = fair_channel(5);
455
456        // Send events from different users
457        for i in 0..6 {
458            let user_id = match i % 3 {
459                0 => "user1",
460                1 => "user2",
461                _ => "user3",
462            };
463            let event = Event {
464                timestamp: i,
465                user_id,
466            };
467            tx.send(event).await.unwrap();
468        }
469
470        // Receive events and verify fair distribution
471        let mut received = Vec::new();
472        for _ in 0..6 {
473            received.push(rx.recv().await.unwrap());
474        }
475
476        // With weighted round-robin, smaller groups are prioritized
477        // After sending: user1=[0,3], user2=[1,4], user3=[2,5]
478        // All groups have equal size, so behavior depends on weighted selection
479        let user1_count = received.iter().filter(|e| e.user_id == "user1").count();
480        let user2_count = received.iter().filter(|e| e.user_id == "user2").count();
481        let user3_count = received.iter().filter(|e| e.user_id == "user3").count();
482
483        // Verify all users get fair representation
484        assert_eq!(user1_count, 2);
485        assert_eq!(user2_count, 2);
486        assert_eq!(user3_count, 2);
487
488        // Verify we received all expected timestamps
489        let mut timestamps: Vec<_> = received.iter().map(|e| e.timestamp).collect();
490        timestamps.sort();
491        assert_eq!(timestamps, vec![0, 1, 2, 3, 4, 5]);
492    }
493
494    #[tokio::test]
495    async fn test_fair_channel_closed_method() {
496        let (tx, mut rx) = fair_channel(5);
497
498        // Start a task that waits for the channel to be closed
499        let tx_clone = tx.clone();
500        let closed_task = tokio::spawn(async move {
501            tx_clone.closed().await;
502        });
503
504        // Give the closed task a moment to start
505        tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
506
507        // Verify the closed task is still running
508        assert!(!closed_task.is_finished());
509
510        // Close the receiver
511        rx.close().await;
512
513        // The closed task should now complete
514        closed_task.await.unwrap();
515
516        // Verify that sending now returns an error
517        let result = tx
518            .send(Event {
519                timestamp: 1,
520                user_id: "user1",
521            })
522            .await;
523        assert!(matches!(result, Err(SendError(_))));
524    }
525
526    // HYPOTHESIS: a group that reached max_group_size is ordinary backpressure
527    // - the sender waits for room - but can_insert reported it at ERROR, and
528    // FairSender::send re-checks after every notify wake-up, so one slow
529    // publication filled prod's server.log with 14k context-free lines
530    // ("Cannot insert value into group: group is full", no group, agent or
531    // publication named).
532    // SOLUTION: report it at DEBUG and name the group and the queue sizes.
533    // RESULT: nothing at ERROR, one DEBUG event carrying the group id.
534    // STATUS: FIXED - see CLOUDPUB-1509
535    #[derive(Clone, Default)]
536    struct LevelCapture(Arc<Mutex<Vec<(tracing::Level, String)>>>);
537
538    impl<S: tracing::Subscriber + for<'a> LookupSpan<'a>> Layer<S> for LevelCapture {
539        fn on_event(&self, event: &tracing::Event<'_>, _ctx: Context<'_, S>) {
540            let mut fields = String::new();
541            event.record(&mut FieldText(&mut fields));
542            self.0
543                .lock()
544                .unwrap()
545                .push((*event.metadata().level(), fields));
546        }
547    }
548
549    struct FieldText<'a>(&'a mut String);
550
551    impl tracing::field::Visit for FieldText<'_> {
552        fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn core::fmt::Debug) {
553            use core::fmt::Write as _;
554            let _ = write!(self.0, " {}={:?}", field.name(), value);
555        }
556    }
557
558    #[test]
559    fn full_group_is_reported_at_debug_with_group_id() {
560        let capture = LevelCapture::default();
561        let subscriber = tracing_subscriber::registry().with(capture.clone());
562
563        with_default(subscriber, || {
564            // max_group_size = 2, then fill user1's group (group_id 1) to it
565            let mut queue: FairQueue<Event> = FairQueue::new(2);
566            let full = Event {
567                timestamp: 1,
568                user_id: "user1",
569            };
570            assert!(queue.insert(Arc::new(full.clone())));
571            assert!(queue.insert(Arc::new(Event {
572                timestamp: 2,
573                user_id: "user1",
574            })));
575            // One more value of that group: the sender would wait here.
576            assert!(!queue.can_insert(&full));
577        });
578
579        let events = capture.0.lock().unwrap();
580        assert!(
581            !events
582                .iter()
583                .any(|(level, _)| *level == tracing::Level::ERROR),
584            "a full group is backpressure, not an error: {events:?}"
585        );
586        let debug_events: Vec<_> = events
587            .iter()
588            .filter(|(level, _)| *level == tracing::Level::DEBUG)
589            .collect();
590        assert_eq!(
591            debug_events.len(),
592            1,
593            "expected exactly one DEBUG event: {events:?}"
594        );
595        assert!(
596            debug_events[0].1.contains("group_id=1"),
597            "the event must name the group that is full: {:?}",
598            debug_events[0]
599        );
600    }
601}