Skip to main content

memberlist_core/
queue.rs

1// use a sync version mutex here is reasonable, because the lock is only held for a short time.
2#![allow(clippy::await_holding_lock)]
3
4use std::{
5  collections::{BTreeSet, HashMap},
6  future::Future,
7  sync::Arc,
8};
9
10use futures::lock::Mutex;
11
12use crate::{broadcast::Broadcast, proto::TinyVec, util::retransmit_limit};
13
14struct Inner<B: Broadcast> {
15  q: BTreeSet<LimitedBroadcast<B>>,
16  m: HashMap<B::Id, LimitedBroadcast<B>>,
17  /// Monotonic broadcast-id source; never reset back to a prior value. The id is part of the
18  /// `(transmits, msg_len, id)` queue key, so reusing an id collides a new broadcast with a
19  /// still-queued same-length one and the second `insert` is silently dropped. `reset()` may
20  /// zero it only because it also clears the queue, leaving nothing to collide with.
21  id_gen: u64,
22}
23
24impl<B: Broadcast> Inner<B> {
25  fn remove(&mut self, item: &LimitedBroadcast<B>) {
26    self.q.remove(item);
27    if let Some(id) = item.broadcast.id() {
28      self.m.remove(id);
29    }
30  }
31
32  fn insert(&mut self, item: LimitedBroadcast<B>) {
33    if let Some(id) = item.broadcast.id() {
34      self.m.insert(id.clone(), item.clone());
35    }
36    self.q.insert(item);
37  }
38
39  /// Returns a pair of min/max values for transmit values
40  /// represented by the current queue contents. Both values represent actual
41  /// transmit values on the interval [0, len).
42  fn get_transmit_range(&self) -> (usize, usize) {
43    if self.q.is_empty() {
44      return (0, 0);
45    }
46    let (min, max) = (self.q.first(), self.q.last());
47    match (min, max) {
48      (Some(min), Some(max)) => (min.transmits, max.transmits),
49      _ => (0, 0),
50    }
51  }
52}
53
54/// Used to calculate the number of nodes in the cluster.
55pub trait NodeCalculator: Send + Sync + 'static {
56  /// Returns the number of nodes in the cluster.
57  fn num_nodes(&self) -> impl Future<Output = usize> + Send;
58}
59
60macro_rules! impl_node_calculator {
61  ($($ty:ty),+$(,)?) => {
62    $(
63      impl NodeCalculator for ::std::sync::Arc<$ty> {
64        async fn num_nodes(&self) -> usize {
65          self.load(::std::sync::atomic::Ordering::SeqCst) as usize
66        }
67      }
68    )*
69  };
70}
71
72impl_node_calculator! {
73  ::core::sync::atomic::AtomicUsize,
74  ::core::sync::atomic::AtomicIsize,
75  ::core::sync::atomic::AtomicU8,
76  ::core::sync::atomic::AtomicI8,
77  ::core::sync::atomic::AtomicU16,
78  ::core::sync::atomic::AtomicI16,
79  ::core::sync::atomic::AtomicU32,
80  ::core::sync::atomic::AtomicI32,
81  ::core::sync::atomic::AtomicU64,
82  ::core::sync::atomic::AtomicI64,
83}
84
85/// Used to queue messages to broadcast to
86/// the cluster (via gossip) but limits the number of transmits per
87/// message. It also prioritizes messages with lower transmit counts
88/// (hence newer messages).
89pub struct TransmitLimitedQueue<B: Broadcast, N> {
90  // num_nodes: Box<dyn Fn() -> usize + Send + Sync + 'static>,
91  num_nodes: N,
92  /// The multiplier used to determine the maximum
93  /// number of retransmissions attempted.
94  retransmit_mult: usize,
95  inner: Mutex<Inner<B>>,
96}
97
98impl<B: Broadcast, N: NodeCalculator> TransmitLimitedQueue<B, N> {
99  // /// Creates a new [`TransmitLimitedQueue`].
100  // pub fn new(retransmit_mult: usize, calc: impl Fn() -> usize + Send + Sync + 'static) -> Self {
101  //   Self {
102  //     num_nodes: Box::new(calc),
103  //     retransmit_mult,
104  //     inner: Mutex::new(Inner {
105  //       q: BTreeSet::new(),
106  //       m: HashMap::new(),
107  //       id_gen: 0,
108  //     }),
109  //   }
110  // }
111
112  /// Creates a new [`TransmitLimitedQueue`].
113  pub fn new(retransmit_mult: usize, calc: N) -> Self {
114    Self {
115      num_nodes: calc,
116      retransmit_mult,
117      inner: Mutex::new(Inner {
118        q: BTreeSet::new(),
119        m: HashMap::new(),
120        id_gen: 0,
121      }),
122    }
123  }
124
125  /// Returns the number of messages queued.
126  pub async fn num_queued(&self) -> usize {
127    self.inner.lock().await.q.len()
128  }
129
130  /// Returns the messages can be broadcast.
131  pub async fn get_broadcasts(&self, limit: usize) -> TinyVec<B::Message> {
132    self.get_broadcast_with_prepend(TinyVec::new(), limit).await
133  }
134
135  pub(crate) async fn get_broadcast_with_prepend(
136    &self,
137    prepend: TinyVec<B::Message>,
138    limit: usize,
139  ) -> TinyVec<B::Message> {
140    let mut to_send = prepend;
141    let mut inner = self.inner.lock().await;
142    if inner.q.is_empty() {
143      return to_send;
144    }
145
146    let transmit_limit = retransmit_limit(self.retransmit_mult, self.num_nodes.num_nodes().await);
147
148    // Visit fresher items first, but only look at stuff that will fit.
149    // We'll go tier by tier, grabbing the largest items first.
150    let (min_tr, max_tr) = inner.get_transmit_range();
151    let mut bytes_used = 0usize;
152    let mut transmits = min_tr;
153    let mut reinsert = Vec::new();
154    while transmits <= max_tr {
155      let free = limit.saturating_sub(bytes_used);
156      if free == 0 {
157        break;
158      }
159
160      let greater_or_equal = Cmp {
161        transmits,
162        msg_len: free as u64,
163        id: u64::MAX,
164      };
165
166      let less_than = Cmp {
167        transmits: transmits + 1,
168        msg_len: u64::MAX,
169        id: u64::MAX,
170      };
171
172      let keep = inner
173        .q
174        .iter()
175        .filter(|item| greater_or_equal <= item && less_than > item)
176        .find(|item| B::encoded_len(item.broadcast.message()) <= free)
177        .cloned();
178
179      match keep {
180        Some(mut keep) => {
181          let msg = keep.broadcast.message();
182          bytes_used += B::encoded_len(msg);
183          // Add to slice to send
184          to_send.push(msg.clone());
185
186          // check if we should stop transmission
187          inner.remove(&keep);
188          if keep.transmits + 1 >= transmit_limit {
189            keep.broadcast.finished().await;
190          } else {
191            // We need to bump this item down to another transmit tier, but
192            // because it would be in the same direction that we're walking the
193            // tiers, we will have to delay the reinsertion until we are
194            // finished our search. Otherwise we'll possibly re-add the message
195            // when we ascend to the next tier.
196            keep.transmits += 1;
197            reinsert.push(keep);
198          }
199        }
200        None => {
201          transmits += 1;
202          continue;
203        }
204      }
205    }
206
207    for item in reinsert {
208      inner.insert(item);
209    }
210
211    to_send
212  }
213
214  /// Used to enqueue a broadcast
215  pub async fn queue_broadcast(&self, b: B) {
216    self.queue_broadcast_in(b, 0).await
217  }
218
219  async fn queue_broadcast_in(&self, b: B, initial_transmits: usize) {
220    let mut inner = self.inner.lock().await;
221    if inner.id_gen == u64::MAX {
222      inner.id_gen = 1;
223    } else {
224      inner.id_gen += 1;
225    }
226    let id = inner.id_gen;
227
228    let lb = LimitedBroadcast {
229      transmits: initial_transmits,
230      msg_len: B::encoded_len(b.message()) as u64,
231      id,
232      broadcast: Arc::new(b),
233    };
234
235    let unique = lb.broadcast.is_unique();
236
237    // Check if this message invalidates another.
238    if let Some(bid) = lb.broadcast.id() {
239      if let Some(old) = inner.m.remove(bid) {
240        old.broadcast.finished().await;
241        inner.remove(&old);
242      }
243    } else if !unique {
244      // Slow path, hopefully nothing hot hits this.
245      for item in inner.q.iter() {
246        let keep = lb.broadcast.invalidates(&item.broadcast);
247        if keep {
248          item.broadcast.finished().await;
249        }
250      }
251      inner
252        .q
253        .retain(|item| !lb.broadcast.invalidates(&item.broadcast));
254    }
255
256    // Append to the relevant queue.
257    inner.insert(lb);
258  }
259
260  /// Clears all the queued messages.
261  pub async fn reset(&self) {
262    let q = {
263      let mut inner = self.inner.lock().await;
264      inner.m.clear();
265      inner.id_gen = 0;
266      std::mem::take(&mut inner.q)
267    };
268
269    for l in q {
270      l.broadcast.finished().await;
271    }
272  }
273
274  /// Retain the `max_retain` latest messages, and the rest
275  /// will be discarded. This can be used to prevent unbounded queue sizes
276  pub async fn prune(&self, max_retain: usize) {
277    let mut inner = self.inner.lock().await;
278    // Do nothing if queue size is less than the limit
279    while inner.q.len() > max_retain {
280      if let Some(item) = inner.q.pop_last() {
281        item.broadcast.finished().await;
282        inner.remove(&item);
283      } else {
284        break;
285      }
286    }
287  }
288}
289
290#[derive(Debug)]
291pub(crate) struct LimitedBroadcast<B: Broadcast> {
292  // btree-key[0]: Number of transmissions attempted.
293  transmits: usize,
294  // btree-key[1]: copied from len(b.Message())
295  msg_len: u64,
296  // btree-key[2]: unique incrementing id stamped at submission time
297  id: u64,
298  pub(crate) broadcast: Arc<B>,
299}
300
301impl<B: Broadcast> core::clone::Clone for LimitedBroadcast<B> {
302  fn clone(&self) -> Self {
303    Self {
304      broadcast: self.broadcast.clone(),
305      ..*self
306    }
307  }
308}
309
310impl<B: Broadcast> PartialEq for LimitedBroadcast<B> {
311  fn eq(&self, other: &Self) -> bool {
312    self.transmits == other.transmits && self.msg_len == other.msg_len && self.id == other.id
313  }
314}
315
316impl<B: Broadcast> Eq for LimitedBroadcast<B> {}
317
318impl<B: Broadcast> PartialOrd for LimitedBroadcast<B> {
319  fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
320    Some(self.cmp(other))
321  }
322}
323
324impl<B: Broadcast> Ord for LimitedBroadcast<B> {
325  fn cmp(&self, other: &Self) -> std::cmp::Ordering {
326    self
327      .transmits
328      .cmp(&other.transmits)
329      .then_with(|| other.msg_len.cmp(&self.msg_len))
330      .then_with(|| other.id.cmp(&self.id))
331  }
332}
333
334struct Cmp {
335  transmits: usize,
336  msg_len: u64,
337  id: u64,
338}
339
340impl<B: Broadcast> PartialEq<&LimitedBroadcast<B>> for Cmp {
341  fn eq(&self, other: &&LimitedBroadcast<B>) -> bool {
342    self.transmits == other.transmits && self.msg_len == other.msg_len && self.id == other.id
343  }
344}
345
346impl<B: Broadcast> PartialOrd<&LimitedBroadcast<B>> for Cmp {
347  fn partial_cmp(&self, other: &&LimitedBroadcast<B>) -> Option<std::cmp::Ordering> {
348    Some(
349      self
350        .transmits
351        .cmp(&other.transmits)
352        .then_with(|| other.msg_len.cmp(&self.msg_len))
353        .then_with(|| other.id.cmp(&self.id)),
354    )
355  }
356}
357
358#[cfg(any(test, feature = "test"))]
359impl<B: Broadcast> Inner<B> {
360  fn walk_read_only<F>(&self, reverse: bool, f: F)
361  where
362    F: FnMut(&LimitedBroadcast<B>) -> bool,
363  {
364    fn iter<'a, B: Broadcast, F>(it: impl Iterator<Item = &'a LimitedBroadcast<B>>, mut f: F)
365    where
366      F: FnMut(&LimitedBroadcast<B>) -> bool,
367    {
368      for item in it {
369        let prev_transmits = item.transmits;
370        let prev_msg_len = item.msg_len;
371        let prev_id = item.id;
372
373        let keep_going = f(item);
374
375        if prev_transmits != item.transmits || prev_msg_len != item.msg_len || prev_id != item.id {
376          panic!("edited queue while walking read only");
377        }
378
379        if !keep_going {
380          break;
381        }
382      }
383    }
384    if reverse {
385      iter(self.q.iter().rev(), f)
386    } else {
387      iter(self.q.iter(), f)
388    }
389  }
390}
391
392#[cfg(any(test, feature = "test"))]
393impl<B: Broadcast, N: NodeCalculator> TransmitLimitedQueue<B, N> {
394  pub(crate) async fn ordered_view(&self, reverse: bool) -> TinyVec<LimitedBroadcast<B>> {
395    let inner = self.inner.lock().await;
396
397    let mut out = TinyVec::new();
398    inner.walk_read_only(reverse, |b| {
399      out.push(b.clone());
400      true
401    });
402    out
403  }
404}
405
406#[cfg(test)]
407mod tests;