use std::collections::HashMap;
use std::sync::{Arc, Mutex, Weak};
use tokio::sync::mpsc::Sender;
pub const STREAMING_CHANNEL_CAPACITY: usize = 256;
use crate::agent_runtime::StreamDelta;
pub type StreamingSinkSender = Arc<Sender<StreamDelta>>;
#[derive(Default)]
pub struct StreamingSinkRegistry {
inner: Mutex<HashMap<String, Weak<Sender<StreamDelta>>>>,
}
impl StreamingSinkRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register(&self, session_id: &str, sender: &StreamingSinkSender) {
let weak = Arc::downgrade(sender);
self.inner
.lock()
.unwrap_or_else(|e| {
tracing::error!("streaming sink mutex poisoned — recovering");
e.into_inner()
})
.insert(session_id.to_string(), weak);
}
pub fn lookup(&self, session_id: &str) -> Option<StreamingSinkSender> {
let guard = self.inner.lock().unwrap_or_else(|e| {
tracing::error!("streaming sink mutex poisoned — recovering");
e.into_inner()
});
guard
.get(session_id)
.and_then(|w| w.upgrade())
.map(|a| Arc::clone(&a))
}
pub fn unregister(&self, session_id: &str) {
self.inner
.lock()
.unwrap_or_else(|e| {
tracing::error!("streaming sink mutex poisoned — recovering");
e.into_inner()
})
.remove(session_id);
}
#[cfg(test)]
pub fn len(&self) -> usize {
self.inner
.lock()
.unwrap_or_else(|e| {
tracing::error!("streaming sink mutex poisoned — recovering");
e.into_inner()
})
.len()
}
#[cfg(test)]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::agent_runtime::StreamingSinkTx;
#[test]
fn lookup_miss_when_empty() {
let r = StreamingSinkRegistry::new();
assert!(r.lookup("missing").is_none());
}
#[test]
fn lookup_returns_strong_when_alive() {
let r = StreamingSinkRegistry::new();
let (tx, _rx) = tokio::sync::mpsc::channel::<StreamDelta>(8);
let sender: StreamingSinkTx = Arc::new(tx);
r.register("s1", &sender);
let looked = r.lookup("s1").expect("should find live sink");
assert!(Arc::ptr_eq(&looked, &sender));
}
#[test]
fn lookup_misses_after_drop() {
let r = StreamingSinkRegistry::new();
let (tx, _rx) = tokio::sync::mpsc::channel::<StreamDelta>(8);
let sender: StreamingSinkTx = Arc::new(tx);
r.register("s1", &sender);
drop(sender);
assert!(r.lookup("s1").is_none(), "stale Weak must not upgrade");
}
#[test]
fn unregister_clears_entry() {
let r = StreamingSinkRegistry::new();
let (tx, _rx) = tokio::sync::mpsc::channel::<StreamDelta>(8);
let sender: StreamingSinkTx = Arc::new(tx);
r.register("s1", &sender);
r.unregister("s1");
assert!(r.lookup("s1").is_none());
assert!(Arc::strong_count(&sender) >= 1);
}
}