nodedb_lite/sync/outbound/
queue.rs1use std::sync::Mutex;
21use std::sync::atomic::{AtomicU64, Ordering};
22
23#[derive(Debug)]
27pub struct BatchIdGen {
28 next: AtomicU64,
29}
30
31impl BatchIdGen {
32 pub const fn new() -> Self {
33 Self {
34 next: AtomicU64::new(1),
35 }
36 }
37
38 pub fn next(&self) -> u64 {
41 self.next.fetch_add(1, Ordering::Relaxed)
42 }
43}
44
45impl Default for BatchIdGen {
46 fn default() -> Self {
47 Self::new()
48 }
49}
50
51pub(crate) fn take_next(counter: &AtomicU64) -> Option<u64> {
56 let mut current = counter.load(Ordering::Relaxed);
57 loop {
58 let advanced = current.checked_add(1)?;
59 match counter.compare_exchange_weak(current, advanced, Ordering::Relaxed, Ordering::Relaxed)
60 {
61 Ok(previous) => return Some(previous),
62 Err(observed) => current = observed,
63 }
64 }
65}
66
67#[derive(Debug)]
70pub struct PendingQueue<T> {
71 items: Mutex<Vec<T>>,
72}
73
74impl<T> Default for PendingQueue<T> {
75 fn default() -> Self {
76 Self::new()
77 }
78}
79
80impl<T> PendingQueue<T> {
81 pub const fn new() -> Self {
82 Self {
83 items: Mutex::new(Vec::new()),
84 }
85 }
86
87 pub fn push(&self, item: T) {
89 if let Ok(mut g) = self.items.lock() {
90 g.push(item);
91 }
92 }
93
94 pub fn drain(&self) -> Vec<T> {
97 match self.items.lock() {
98 Ok(mut g) => std::mem::take(&mut *g),
99 Err(_) => Vec::new(),
100 }
101 }
102
103 pub fn requeue(&self, item: T) {
106 if let Ok(mut g) = self.items.lock() {
107 g.insert(0, item);
108 }
109 }
110
111 pub fn retain<F>(&self, predicate: F)
114 where
115 F: FnMut(&T) -> bool,
116 {
117 if let Ok(mut g) = self.items.lock() {
118 g.retain(predicate);
119 }
120 }
121
122 pub fn with_first_mut<P, F, R>(&self, mut predicate: P, f: F) -> Option<R>
128 where
129 P: FnMut(&T) -> bool,
130 F: FnOnce(&mut T) -> R,
131 {
132 let mut g = self.items.lock().ok()?;
133 let entry = g.iter_mut().find(|t| predicate(t))?;
134 Some(f(entry))
135 }
136
137 pub fn len(&self) -> usize {
139 self.items.lock().map(|g| g.len()).unwrap_or(0)
140 }
141
142 pub fn is_empty(&self) -> bool {
143 self.len() == 0
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn batch_id_gen_is_monotonic_and_nonzero() {
153 let g = BatchIdGen::new();
154 let a = g.next();
155 let b = g.next();
156 let c = g.next();
157 assert!(a > 0 && b > a && c > b);
158 }
159
160 #[test]
161 fn take_next_stops_at_the_counter_limit() {
162 let counter = AtomicU64::new(u64::MAX - 1);
163 assert_eq!(take_next(&counter), Some(u64::MAX - 1));
164 assert_eq!(take_next(&counter), None);
165 assert_eq!(counter.load(Ordering::Relaxed), u64::MAX);
166 }
167
168 #[test]
169 fn push_drain_round_trip() {
170 let q = PendingQueue::<u32>::new();
171 q.push(1);
172 q.push(2);
173 let out = q.drain();
174 assert_eq!(out, vec![1, 2]);
175 assert!(q.drain().is_empty());
176 }
177
178 #[test]
179 fn requeue_inserts_at_head() {
180 let q = PendingQueue::<u32>::new();
181 q.push(1);
182 q.requeue(99);
183 assert_eq!(q.drain(), vec![99, 1]);
184 }
185
186 #[test]
187 fn retain_removes_matches() {
188 let q = PendingQueue::<u32>::new();
189 for i in 0..5 {
190 q.push(i);
191 }
192 q.retain(|x| *x % 2 == 0);
193 assert_eq!(q.drain(), vec![0, 2, 4]);
194 }
195
196 #[test]
197 fn with_first_mut_targets_match_only() {
198 let q = PendingQueue::<(u32, u32)>::new();
199 q.push((1, 10));
200 q.push((2, 20));
201 let touched = q.with_first_mut(|x| x.0 == 2, |x| x.1 += 5);
202 assert_eq!(touched, Some(()));
203 assert_eq!(q.drain(), vec![(1, 10), (2, 25)]);
204 }
205}