use std::sync::{Arc, RwLock};
use tokio::sync::broadcast;
use super::resync::ResyncMarker;
pub const DEFAULT_MULTICAST_CAPACITY: usize = 1024;
#[derive(Clone)]
pub struct Dispatcher {
inner: Arc<DispatcherInner>,
}
struct DispatcherInner {
subscribers: RwLock<std::collections::HashMap<u32, broadcast::Sender<DispatcherEvent>>>,
}
#[derive(Debug, Clone)]
pub enum DispatcherEvent {
Frame(Arc<Vec<u8>>),
Resync(ResyncMarker),
}
impl Dispatcher {
pub(crate) fn new() -> Self {
Self {
inner: Arc::new(DispatcherInner {
subscribers: RwLock::new(std::collections::HashMap::new()),
}),
}
}
pub fn subscribe_multicast(&self, group: u32) -> broadcast::Receiver<DispatcherEvent> {
let mut subs = self.inner.subscribers.write().unwrap_or_else(|p| p.into_inner());
subs.entry(group)
.or_insert_with(|| broadcast::channel(DEFAULT_MULTICAST_CAPACITY).0)
.subscribe()
}
pub fn emit_enobufs(&self) {
let subs = self.inner.subscribers.read().unwrap_or_else(|p| p.into_inner());
let ev = DispatcherEvent::Resync(ResyncMarker::ResyncStart);
for (group, sender) in subs.iter() {
let n = sender.receiver_count();
if n == 0 {
continue;
}
match sender.send(ev.clone()) {
Ok(_) => {
tracing::debug!(group, subs = n, "dispatcher: ENOBUFS resync emitted");
}
Err(_) => {
tracing::trace!(
group,
"dispatcher: ENOBUFS resync had no live receivers"
);
}
}
}
}
pub fn fan_out(&self, group: u32, frame: Arc<Vec<u8>>) -> usize {
let subs = self.inner.subscribers.read().unwrap_or_else(|p| p.into_inner());
let Some(sender) = subs.get(&group) else {
return 0;
};
let n = sender.receiver_count();
if n == 0 {
return 0;
}
let _ = sender.send(DispatcherEvent::Frame(frame));
n
}
pub fn active_group_count(&self) -> usize {
self.inner.subscribers.read().unwrap_or_else(|p| p.into_inner()).len()
}
#[cfg(test)]
pub fn gc(&self) {
let mut subs = self.inner.subscribers.write().unwrap_or_else(|p| p.into_inner());
subs.retain(|_, sender| sender.receiver_count() > 0);
}
}
impl Default for Dispatcher {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for Dispatcher {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let n = self.active_group_count();
f.debug_struct("Dispatcher").field("active_groups", &n).finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn dispatcher_starts_empty() {
let d = Dispatcher::new();
assert_eq!(d.active_group_count(), 0);
}
#[tokio::test]
async fn subscribe_multicast_registers_group() {
let d = Dispatcher::new();
let _rx = d.subscribe_multicast(1);
assert_eq!(d.active_group_count(), 1);
}
#[tokio::test]
async fn subscribe_multicast_same_group_shares_sender() {
let d = Dispatcher::new();
let _rx1 = d.subscribe_multicast(7);
let _rx2 = d.subscribe_multicast(7);
assert_eq!(d.active_group_count(), 1);
}
#[tokio::test]
async fn fan_out_delivers_to_subscriber() {
let d = Dispatcher::new();
let mut rx = d.subscribe_multicast(42);
let frame = Arc::new(vec![1u8, 2, 3, 4]);
let n = d.fan_out(42, frame.clone());
assert_eq!(n, 1);
match rx.recv().await {
Ok(DispatcherEvent::Frame(f)) => assert_eq!(&*f, &[1u8, 2, 3, 4]),
other => panic!("expected Frame, got {:?}", other),
}
}
#[tokio::test]
async fn fan_out_skips_other_groups() {
let d = Dispatcher::new();
let mut rx1 = d.subscribe_multicast(1);
let _rx2 = d.subscribe_multicast(2); let frame = Arc::new(vec![9u8]);
let n = d.fan_out(2, frame);
assert_eq!(n, 1, "fan_out reaches the group-2 subscriber");
assert!(
matches!(rx1.try_recv(), Err(broadcast::error::TryRecvError::Empty)),
"group-1 receiver must not see group-2 frame"
);
}
#[tokio::test]
async fn fan_out_no_subscribers_returns_zero() {
let d = Dispatcher::new();
let frame = Arc::new(vec![1u8]);
assert_eq!(d.fan_out(1, frame), 0);
}
#[tokio::test]
async fn enobufs_fans_out_resync_start_to_all_subscribers() {
let d = Dispatcher::new();
let mut rx1 = d.subscribe_multicast(1);
let mut rx2 = d.subscribe_multicast(2);
let mut rx3 = d.subscribe_multicast(3);
d.emit_enobufs();
for (i, rx) in [&mut rx1, &mut rx2, &mut rx3].iter_mut().enumerate() {
match rx.recv().await {
Ok(DispatcherEvent::Resync(ResyncMarker::ResyncStart)) => {
}
other => panic!(
"subscriber {i} expected ResyncStart, got {:?}",
other
),
}
}
}
#[tokio::test]
async fn enobufs_with_no_subscribers_is_noop() {
let d = Dispatcher::new();
d.emit_enobufs();
let mut rx = d.subscribe_multicast(99);
assert!(matches!(
rx.try_recv(),
Err(broadcast::error::TryRecvError::Empty)
));
}
#[tokio::test]
async fn enobufs_with_dropped_receiver_does_not_panic() {
let d = Dispatcher::new();
let rx = d.subscribe_multicast(5);
drop(rx);
d.emit_enobufs();
}
#[tokio::test]
async fn gc_reclaims_dropped_groups() {
let d = Dispatcher::new();
let rx = d.subscribe_multicast(11);
assert_eq!(d.active_group_count(), 1);
drop(rx);
d.gc();
assert_eq!(d.active_group_count(), 0);
}
#[tokio::test]
async fn fan_out_with_many_subscribers() {
let d = Dispatcher::new();
let mut receivers: Vec<_> = (0..32).map(|_| d.subscribe_multicast(7)).collect();
let frame = Arc::new(vec![0xAB; 16]);
let delivered = d.fan_out(7, frame.clone());
assert_eq!(delivered, 32);
for rx in receivers.iter_mut() {
match rx.try_recv() {
Ok(DispatcherEvent::Frame(f)) => assert_eq!(f.len(), 16),
other => panic!("expected Frame, got {:?}", other),
}
}
}
#[tokio::test]
async fn debug_impl_exposes_active_group_count() {
let d = Dispatcher::new();
let _ = d.subscribe_multicast(1);
let _ = d.subscribe_multicast(2);
let s = format!("{:?}", d);
assert!(s.contains("active_groups: 2"), "got {s}");
}
#[tokio::test]
async fn connection_install_propagates_enobufs_to_subscribers() {
use crate::netlink::{Connection, Route};
let conn = Connection::<Route>::new().unwrap();
let dispatcher = conn.dispatcher();
let mut rx = dispatcher.subscribe_multicast(1);
let installed = conn
.socket()
.dispatcher_for_test()
.expect("Connection::new must install_dispatcher");
assert_eq!(installed.active_group_count(), 1);
conn.socket().synth_enobufs_for_test();
match rx.recv().await {
Ok(DispatcherEvent::Resync(ResyncMarker::ResyncStart)) => {}
other => panic!("expected ResyncStart, got {:?}", other),
}
}
}