use std::collections::HashMap;
use tokio::task::JoinHandle;
#[derive(Default)]
pub struct SubscriptionRegistry {
counters: HashMap<&'static str, u64>,
handles: HashMap<String, JoinHandle<()>>,
}
impl SubscriptionRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, namespace: &'static str, handle: JoinHandle<()>) -> String {
let n = self.counters.entry(namespace).or_insert(0);
*n += 1;
let id = format!("sub-{namespace}-{n}");
self.handles.insert(id.clone(), handle);
id
}
pub fn unsubscribe(&mut self, id: &str) -> bool {
if let Some(handle) = self.handles.remove(id) {
handle.abort();
true
} else {
false
}
}
#[cfg(test)]
pub fn len(&self) -> usize {
self.handles.len()
}
}
impl Drop for SubscriptionRegistry {
fn drop(&mut self) {
for (_, handle) in self.handles.drain() {
handle.abort();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn dummy_handle() -> JoinHandle<()> {
tokio::spawn(async {
std::future::pending::<()>().await;
})
}
#[tokio::test]
async fn register_issues_namespaced_monotonic_ids() {
let mut reg = SubscriptionRegistry::new();
let id1 = reg.register("s", dummy_handle());
let id2 = reg.register("s", dummy_handle());
let id3 = reg.register("n", dummy_handle());
assert_eq!(id1, "sub-s-1");
assert_eq!(id2, "sub-s-2");
assert_eq!(id3, "sub-n-1");
assert_eq!(reg.len(), 3);
}
#[tokio::test]
async fn unsubscribe_aborts_handle_and_returns_true() {
let mut reg = SubscriptionRegistry::new();
let id = reg.register("s", dummy_handle());
assert!(reg.unsubscribe(&id));
assert_eq!(reg.len(), 0);
assert!(!reg.unsubscribe(&id));
}
#[tokio::test]
async fn drop_aborts_all_outstanding_handles() {
let mut reg = SubscriptionRegistry::new();
let _ = reg.register("s", dummy_handle());
let _ = reg.register("n", dummy_handle());
let _ = reg.register("j", dummy_handle());
assert_eq!(reg.len(), 3);
drop(reg);
}
}