use std::sync::{mpsc, Arc, Weak};
use std::time::Duration;
use parking_lot::Mutex;
use crate::state::ChangeEvent;
pub(crate) struct EventFanout {
inner: Mutex<FanoutInner>,
}
struct FanoutInner {
next_id: u64,
subscribers: Vec<(u64, mpsc::Sender<ChangeEvent>)>,
}
impl EventFanout {
pub(crate) fn new() -> Self {
Self {
inner: Mutex::new(FanoutInner {
next_id: 0,
subscribers: Vec::new(),
}),
}
}
pub(crate) fn subscribe(&self) -> (u64, mpsc::Receiver<ChangeEvent>) {
let (tx, rx) = mpsc::channel();
let mut inner = self.inner.lock();
let id = inner.next_id;
inner.next_id += 1;
inner.subscribers.push((id, tx));
tracing::trace!(
"EventFanout: subscriber {} registered ({} total)",
id,
inner.subscribers.len()
);
(id, rx)
}
pub(crate) fn unsubscribe(&self, id: u64) {
let mut inner = self.inner.lock();
inner.subscribers.retain(|(sub_id, _)| *sub_id != id);
tracing::trace!(
"EventFanout: subscriber {} removed ({} remain)",
id,
inner.subscribers.len()
);
}
pub(crate) fn send(&self, event: ChangeEvent) -> usize {
let mut inner = self.inner.lock();
let mut delivered = 0usize;
inner
.subscribers
.retain(|(_, tx)| match tx.send(event.clone()) {
Ok(()) => {
delivered += 1;
true
}
Err(_) => false,
});
delivered
}
#[cfg(test)]
pub(crate) fn subscriber_count(&self) -> usize {
self.inner.lock().subscribers.len()
}
}
pub struct ChangeIterator {
fanout: Weak<EventFanout>,
id: u64,
rx: Mutex<mpsc::Receiver<ChangeEvent>>,
}
impl ChangeIterator {
pub(crate) fn new(fanout: &Arc<EventFanout>) -> Self {
let (id, rx) = fanout.subscribe();
Self {
fanout: Arc::downgrade(fanout),
id,
rx: Mutex::new(rx),
}
}
pub fn recv(&self) -> Option<ChangeEvent> {
let event = self.rx.lock().recv().ok();
if let Some(ref e) = event {
tracing::trace!(
"ChangeIterator::recv yielded {} for {}",
e.property_key(),
e.speaker_id.as_str()
);
}
event
}
pub fn recv_timeout(&self, timeout: Duration) -> Option<ChangeEvent> {
let event = self.rx.lock().recv_timeout(timeout).ok();
if let Some(ref e) = event {
tracing::trace!(
"ChangeIterator::recv_timeout yielded {} for {}",
e.property_key(),
e.speaker_id.as_str()
);
}
event
}
pub fn try_recv(&self) -> Option<ChangeEvent> {
let event = self.rx.lock().try_recv().ok();
if let Some(ref e) = event {
tracing::trace!(
"ChangeIterator::try_recv yielded {} for {}",
e.property_key(),
e.speaker_id.as_str()
);
}
event
}
pub fn try_iter(&self) -> TryIter<'_> {
TryIter { inner: self }
}
pub fn timeout_iter(&self, timeout: Duration) -> TimeoutIter<'_> {
TimeoutIter {
inner: self,
timeout,
}
}
}
impl Drop for ChangeIterator {
fn drop(&mut self) {
if let Some(fanout) = self.fanout.upgrade() {
fanout.unsubscribe(self.id);
}
}
}
impl Iterator for ChangeIterator {
type Item = ChangeEvent;
fn next(&mut self) -> Option<Self::Item> {
self.recv()
}
}
pub struct TryIter<'a> {
inner: &'a ChangeIterator,
}
impl<'a> Iterator for TryIter<'a> {
type Item = ChangeEvent;
fn next(&mut self) -> Option<Self::Item> {
self.inner.try_recv()
}
}
pub struct TimeoutIter<'a> {
inner: &'a ChangeIterator,
timeout: Duration,
}
impl<'a> Iterator for TimeoutIter<'a> {
type Item = ChangeEvent;
fn next(&mut self) -> Option<Self::Item> {
self.inner.recv_timeout(self.timeout)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::decoder::PropertyChange;
use crate::model::SpeakerId;
use crate::property::Volume;
use crate::state::{ChangeSource, WriteStamp};
use std::thread;
use std::time::Instant;
fn create_test_event() -> ChangeEvent {
event_with_volume(42)
}
fn event_with_volume(v: u8) -> ChangeEvent {
ChangeEvent::new(
SpeakerId::new("test-speaker"),
PropertyChange::Volume(Volume::new(v)),
WriteStamp::now(ChangeSource::Event),
)
}
fn volume_of(event: &ChangeEvent) -> u8 {
match &event.change {
PropertyChange::Volume(v) => v.value(),
other => panic!("expected a Volume change, got {other:?}"),
}
}
fn fanout_with_iter() -> (Arc<EventFanout>, ChangeIterator) {
let fanout = Arc::new(EventFanout::new());
let iter = ChangeIterator::new(&fanout);
(fanout, iter)
}
#[test]
fn test_try_recv_empty() {
let (_fanout, iter) = fanout_with_iter();
assert!(iter.try_recv().is_none());
}
#[test]
fn test_try_recv_with_event() {
let (fanout, iter) = fanout_with_iter();
fanout.send(create_test_event());
let event = iter.try_recv().unwrap();
assert_eq!(event.property_key(), "volume");
assert_eq!(event.speaker_id.as_str(), "test-speaker");
assert!(iter.try_recv().is_none());
}
#[test]
fn test_recv_timeout() {
let (_fanout, iter) = fanout_with_iter();
let start = Instant::now();
let result = iter.recv_timeout(Duration::from_millis(50));
assert!(result.is_none());
assert!(start.elapsed() >= Duration::from_millis(45));
}
#[test]
fn test_recv_timeout_with_event() {
let (fanout, iter) = fanout_with_iter();
let sender = Arc::clone(&fanout);
thread::spawn(move || {
thread::sleep(Duration::from_millis(10));
sender.send(create_test_event());
});
let result = iter.recv_timeout(Duration::from_millis(500));
assert!(result.is_some());
}
#[test]
fn test_try_iter() {
let (fanout, iter) = fanout_with_iter();
for _ in 0..3 {
fanout.send(create_test_event());
}
let events: Vec<_> = iter.try_iter().collect();
assert_eq!(events.len(), 3);
assert!(iter.try_recv().is_none());
}
#[test]
fn test_blocking_recv() {
let (fanout, iter) = fanout_with_iter();
let sender = Arc::clone(&fanout);
thread::spawn(move || {
thread::sleep(Duration::from_millis(10));
sender.send(create_test_event());
});
let event = iter.recv().unwrap();
assert_eq!(event.property_key(), "volume");
}
#[test]
fn test_channel_closed() {
let (fanout, iter) = fanout_with_iter();
let (done_tx, done_rx) = mpsc::channel();
thread::spawn(move || {
let got = iter.recv();
let _ = done_tx.send(got);
});
drop(fanout);
match done_rx.recv_timeout(Duration::from_secs(5)) {
Ok(None) => {} Ok(Some(e)) => panic!("expected no event from a closed fan-out, got {e:?}"),
Err(err) => panic!(
"recv() did not return after the fan-out was dropped ({err:?}) — the \
iterator is holding its own sender alive (should be `Weak`)"
),
}
}
#[test]
fn test_slow_subscriber_loses_no_events() {
let fanout = Arc::new(EventFanout::new());
let fast = ChangeIterator::new(&fanout);
let slow = ChangeIterator::new(&fanout);
for v in 0..100u8 {
assert_eq!(
fanout.send(event_with_volume(v)),
2,
"both subscribers must be delivered to"
);
}
let fast_seen: Vec<u8> = fast.try_iter().map(|e| volume_of(&e)).collect();
assert_eq!(fast_seen, (0..100u8).collect::<Vec<_>>());
let slow_seen: Vec<u8> = slow.try_iter().map(|e| volume_of(&e)).collect();
assert_eq!(slow_seen, (0..100u8).collect::<Vec<_>>());
}
#[test]
fn test_dropped_iterator_deregisters_immediately() {
let fanout = Arc::new(EventFanout::new());
let keep = ChangeIterator::new(&fanout);
let discard = ChangeIterator::new(&fanout);
assert_eq!(fanout.subscriber_count(), 2);
drop(discard);
assert_eq!(
fanout.subscriber_count(),
1,
"a dropped ChangeIterator must deregister itself on drop"
);
assert_eq!(fanout.send(event_with_volume(9)), 1);
assert_eq!(volume_of(&keep.recv().unwrap()), 9);
}
#[test]
fn test_send_reaps_dead_subscriber() {
let fanout = Arc::new(EventFanout::new());
let keep = ChangeIterator::new(&fanout);
let (_id, raw_rx) = fanout.subscribe();
assert_eq!(fanout.subscriber_count(), 2);
drop(raw_rx);
assert_eq!(fanout.subscriber_count(), 2);
assert_eq!(fanout.send(event_with_volume(5)), 1);
assert_eq!(
fanout.subscriber_count(),
1,
"a send to a dead subscriber must remove it"
);
assert_eq!(volume_of(&keep.recv().unwrap()), 5);
}
}