this catches some amount of spurious notifications (accidental like + quick unlike) and also possibly some cases of bsky moderation actions? or maybe would need to listen to labellers for that (and prob should)
···11pub mod consumer;
22+pub mod delay;
23pub mod error;
34pub mod server;
45pub mod subscriber;
66+pub mod removable_delay_queue;
5768use serde::Serialize;
79
···11+use std::ops::RangeBounds;
22+use std::collections::{BTreeMap, VecDeque};
33+use std::time::{Duration, Instant};
44+use tokio::sync::Mutex;
55+use std::sync::Arc;
66+use thiserror::Error;
77+88+#[derive(Debug, Error)]
99+pub enum EnqueueError<T> {
1010+ #[error("queue ouput dropped")]
1111+ OutputDropped(T),
1212+}
1313+1414+pub trait Key: Eq + Ord + Clone {}
1515+impl<T: Eq + Ord + Clone> Key for T {}
1616+1717+#[derive(Debug)]
1818+struct Queue<K: Key, T> {
1919+ queue: VecDeque<(Instant, K)>,
2020+ items: BTreeMap<K, T>
2121+}
2222+2323+pub struct Input<K: Key, T> {
2424+ q: Arc<Mutex<Queue<K, T>>>,
2525+}
2626+2727+impl<K: Key, T> Input<K, T> {
2828+ /// if a key is already present, its previous item will be overwritten and
2929+ /// its delay time will be reset for the new item.
3030+ ///
3131+ /// errors if the remover has been dropped
3232+ pub async fn enqueue(&self, key: K, item: T) -> Result<(), EnqueueError<T>> {
3333+ if Arc::strong_count(&self.q) == 1 {
3434+ return Err(EnqueueError::OutputDropped(item));
3535+ }
3636+ // TODO: try to push out an old element first
3737+ // for now we just hope there's a listener
3838+ let now = Instant::now();
3939+ let mut q = self.q.lock().await;
4040+ q.queue.push_back((now, key.clone()));
4141+ q.items.insert(key, item);
4242+ Ok(())
4343+ }
4444+ /// remove an item from the queue, by key
4545+ ///
4646+ /// the item itself is removed, but the key will remain in the queue -- it
4747+ /// will simply be skipped over when a new output item is requested. this
4848+ /// keeps the removal cheap (=btreemap remove), for a bit of space overhead
4949+ pub async fn remove_range(&self, range: impl RangeBounds<K>) {
5050+ let n = {
5151+ let mut q = self.q.lock().await;
5252+ let keys = q.items.range(range).map(|(k, _)| k).cloned().collect::<Vec<_>>();
5353+ for k in &keys {
5454+ q.items.remove(k);
5555+ }
5656+ keys.len()
5757+ };
5858+ if n == 0 {
5959+ metrics::counter!("delay_queue_remove_not_found").increment(1);
6060+ } else {
6161+ metrics::counter!("delay_queue_remove_total_records").increment(1);
6262+ metrics::counter!("delay_queue_remove_total_links").increment(n as u64);
6363+ }
6464+ }
6565+}
6666+6767+pub struct Output<K: Key, T> {
6868+ delay: Duration,
6969+ q: Arc<Mutex<Queue<K, T>>>,
7070+}
7171+7272+impl<K: Key, T> Output<K, T> {
7373+ pub async fn next(&self) -> Option<T> {
7474+ let get = || async {
7575+ let mut q = self.q.lock().await;
7676+ while let Some((t, k)) = q.queue.pop_front() {
7777+ // skip over queued keys that were removed from items
7878+ if let Some(item) = q.items.remove(&k) {
7979+ return Some((t, item));
8080+ }
8181+ }
8282+ None
8383+ };
8484+ loop {
8585+ if let Some((t, item)) = get().await {
8686+ let expected_release = t + self.delay;
8787+ let now = Instant::now();
8888+ if expected_release > now {
8989+ tokio::time::sleep_until(expected_release.into()).await;
9090+ metrics::counter!("delay_queue_emit_total", "early" => "yes").increment(1);
9191+ } else {
9292+ metrics::counter!("delay_queue_emit_total", "early" => "no").increment(1);
9393+ let overshoot = now - expected_release;
9494+ metrics::histogram!("delay_queue_emit_overshoot").record(overshoot.as_secs_f64());
9595+ }
9696+ return Some(item)
9797+ } else if Arc::strong_count(&self.q) == 1 {
9898+ return None;
9999+ }
100100+ // the queue is *empty*, so we need to wait at least as long as the current delay
101101+ tokio::time::sleep(self.delay).await;
102102+ metrics::counter!("delay_queue_entirely_empty_total").increment(1);
103103+ };
104104+ }
105105+}
106106+107107+pub fn removable_delay_queue<K: Key, T>(
108108+ delay: Duration,
109109+) -> (Input<K, T>, Output<K, T>) {
110110+ let q: Arc<Mutex<Queue<K, T>>> = Arc::new(Mutex::new(Queue {
111111+ queue: VecDeque::new(),
112112+ items: BTreeMap::new(),
113113+ }));
114114+115115+ let input = Input::<K, T> { q: q.clone() };
116116+ let output = Output::<K, T> { q, delay };
117117+ (input, output)
118118+}