use commonware_utils::vec::NonEmptyVec;
use std::collections::{btree_map::Entry as BTreeMapEntry, BTreeMap};
#[derive(Clone, Debug)]
pub struct Tracker<K, S> {
entries: BTreeMap<K, BTreeMap<S, tracing::Span>>,
}
impl<K, S> Default for Tracker<K, S> {
fn default() -> Self {
Self {
entries: BTreeMap::new(),
}
}
}
impl<K, S> Tracker<K, S>
where
K: Clone + Ord,
S: Clone + Ord,
{
pub fn new() -> Self {
Self::default()
}
pub fn contains(&self, key: &K) -> bool {
self.entries.contains_key(key)
}
pub fn insert(&mut self, key: K, subscribers: NonEmptyVec<(S, tracing::Span)>) -> bool {
let (entry, new) = match self.entries.entry(key) {
BTreeMapEntry::Vacant(entry) => (entry.insert(BTreeMap::new()), true),
BTreeMapEntry::Occupied(entry) => (entry.into_mut(), false),
};
for (subscriber, span) in subscribers {
entry.entry(subscriber).or_insert(span);
}
new
}
pub fn remove(&mut self, key: &K) -> bool {
self.entries.remove(key).is_some()
}
pub fn clear(&mut self) {
self.entries.clear();
}
pub fn retain<F>(&mut self, mut predicate: F) -> Vec<K>
where
F: FnMut(&K, &S) -> bool,
{
let mut removed = Vec::new();
self.entries.retain(|key, subscribers| {
subscribers.retain(|subscriber, _| predicate(key, subscriber));
let keep = !subscribers.is_empty();
if !keep {
removed.push(key.clone());
}
keep
});
removed
}
pub fn pending(&self, key: &K) -> Option<NonEmptyVec<(S, tracing::Span)>> {
self.entries.get(key).and_then(Self::non_empty)
}
pub fn remove_delivered(
&mut self,
key: &K,
delivered: NonEmptyVec<S>,
) -> Option<NonEmptyVec<(S, tracing::Span)>> {
let entry = self.entries.get_mut(key)?;
for subscriber in delivered {
entry.remove(&subscriber);
}
if entry.is_empty() {
self.entries.remove(key);
return None;
}
self.pending(key)
}
fn non_empty(
subscribers: &BTreeMap<S, tracing::Span>,
) -> Option<NonEmptyVec<(S, tracing::Span)>> {
NonEmptyVec::try_from(
subscribers
.iter()
.map(|(subscriber, span)| (subscriber.clone(), span.clone()))
.collect::<Vec<_>>(),
)
.ok()
}
}
#[cfg(test)]
mod tests {
use super::*;
use commonware_utils::non_empty_vec;
fn subscribers<S: Clone>(pending: Option<NonEmptyVec<(S, tracing::Span)>>) -> Option<Vec<S>> {
pending.map(|pending| {
pending
.into_iter()
.map(|(subscriber, _)| subscriber)
.collect()
})
}
fn none<S>(subscribers: NonEmptyVec<S>) -> NonEmptyVec<(S, tracing::Span)> {
NonEmptyVec::from_unchecked(
subscribers
.into_iter()
.map(|subscriber| (subscriber, tracing::Span::none()))
.collect(),
)
}
#[test]
fn insert_merges_and_deduplicates_subscribers() {
let mut tracker = Tracker::new();
assert!(tracker.insert(1, none(non_empty_vec![10, 11])));
assert!(!tracker.insert(1, none(non_empty_vec![11, 12])));
assert_eq!(subscribers(tracker.pending(&1)), Some(vec![10, 11, 12]));
}
#[test]
fn retain_prunes_subscribers_and_reports_removed_keys() {
let mut tracker = Tracker::new();
tracker.insert(1, none(non_empty_vec![11]));
tracker.insert(2, none(non_empty_vec![20]));
let removed = tracker.retain(|_, subscriber| *subscriber % 2 == 0);
assert_eq!(removed, vec![1]);
assert!(!tracker.contains(&1));
assert_eq!(subscribers(tracker.pending(&2)), Some(vec![20]));
}
#[test]
fn remove_delivered_returns_remaining_subscribers() {
let mut tracker = Tracker::new();
tracker.insert(1, none(non_empty_vec![10, 11, 12]));
let remaining = tracker.remove_delivered(&1, non_empty_vec![10, 12]);
assert_eq!(subscribers(remaining), Some(vec![11]));
assert!(tracker.contains(&1));
}
#[test]
fn remove_delivered_removes_completed_key() {
let mut tracker = Tracker::new();
tracker.insert(1, none(non_empty_vec![10, 11]));
assert!(tracker
.remove_delivered(&1, non_empty_vec![10, 11])
.is_none());
assert!(!tracker.contains(&1));
}
#[test]
fn each_subscriber_keeps_its_own_fetch_span() {
let _guard = tracing::subscriber::set_default(tracing_subscriber::registry());
let first = tracing::info_span!("test.first_fetch");
let second = tracing::info_span!("test.second_fetch");
let first_id = first.id();
let second_id = second.id();
assert!(first_id.is_some());
assert_ne!(first_id, second_id);
let mut tracker = Tracker::new();
assert!(tracker.insert(1, non_empty_vec![(10, first)]));
assert!(!tracker.insert(1, non_empty_vec![(10, second.clone()), (11, second)]));
let spans: BTreeMap<i32, Option<tracing::Id>> = tracker
.pending(&1)
.unwrap()
.into_iter()
.map(|(subscriber, span)| (subscriber, span.id()))
.collect();
assert_eq!(spans.get(&10), Some(&first_id));
assert_eq!(spans.get(&11), Some(&second_id));
assert!(tracker.pending(&2).is_none());
}
}