use std::sync::Arc;
use dashmap::DashMap;
#[derive(Default)]
pub struct SessionWatchers {
counts: DashMap<String, usize>,
}
impl SessionWatchers {
pub fn new() -> Arc<Self> {
Arc::new(Self::default())
}
fn watch(&self, session_id: &str) {
*self.counts.entry(session_id.to_string()).or_insert(0) += 1;
}
fn unwatch(&self, session_id: &str) {
let mut drop_entry = false;
if let Some(mut count) = self.counts.get_mut(session_id) {
*count = count.saturating_sub(1);
drop_entry = *count == 0;
}
if drop_entry {
self.counts.remove(session_id);
}
}
pub fn has_watcher(&self, session_id: &str) -> bool {
self.counts.get(session_id).is_some_and(|count| *count > 0)
}
}
pub struct WatcherGuard {
watchers: Arc<SessionWatchers>,
session_id: String,
}
impl WatcherGuard {
pub fn new(watchers: Arc<SessionWatchers>, session_id: &str) -> Self {
watchers.watch(session_id);
Self {
watchers,
session_id: session_id.to_string(),
}
}
}
impl Drop for WatcherGuard {
fn drop(&mut self) {
self.watchers.unwatch(&self.session_id);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn watch_and_unwatch_tracks_presence() {
let watchers = SessionWatchers::new();
assert!(!watchers.has_watcher("s1"));
let guard_one = WatcherGuard::new(watchers.clone(), "s1");
assert!(watchers.has_watcher("s1"));
let guard_two = WatcherGuard::new(watchers.clone(), "s1");
assert!(watchers.has_watcher("s1"));
drop(guard_one);
assert!(watchers.has_watcher("s1"), "one watcher remains");
drop(guard_two);
assert!(!watchers.has_watcher("s1"));
}
#[test]
fn distinct_sessions_are_tracked_independently() {
let watchers = SessionWatchers::new();
let _guard = WatcherGuard::new(watchers.clone(), "s1");
assert!(watchers.has_watcher("s1"));
assert!(!watchers.has_watcher("s2"));
}
#[test]
fn repeated_watch_unwatch_never_underflows_or_panics() {
let watchers = SessionWatchers::new();
for _ in 0..5 {
let guard = WatcherGuard::new(watchers.clone(), "s3");
drop(guard);
}
assert!(!watchers.has_watcher("s3"));
}
}