use std::sync::{Arc, Mutex, mpsc};
use tokio_util::sync::CancellationToken;
use super::DiscoveryEvent;
pub(super) const EVENT_CAPACITY: usize = 4_096;
#[derive(Debug, Default)]
struct State {
overload_reason: Mutex<Option<String>>,
}
#[derive(Debug, Clone)]
pub(super) struct EventSender {
sender: mpsc::SyncSender<DiscoveryEvent>,
state: Arc<State>,
shutdown: CancellationToken,
capacity: usize,
}
#[derive(Debug, Clone)]
pub(super) struct InboxControl {
state: Arc<State>,
}
impl EventSender {
pub(super) fn send(&self, event: DiscoveryEvent) -> Result<(), ()> {
match self.sender.try_send(event) {
Ok(()) => Ok(()),
Err(mpsc::TrySendError::Full(_)) => {
self.overload(format!(
"discovery produced more than {} pending events; records were cleared; refresh to retry",
self.capacity
));
Err(())
}
Err(mpsc::TrySendError::Disconnected(_)) => Err(()),
}
}
pub(super) fn overload(&self, reason: impl Into<String>) {
let mut slot = self
.state
.overload_reason
.lock()
.unwrap_or_else(|err| err.into_inner());
if slot.is_none() {
*slot = Some(reason.into());
}
drop(slot);
self.shutdown.cancel();
}
}
impl InboxControl {
pub(super) fn overload_reason(&self) -> Option<String> {
self.state
.overload_reason
.lock()
.unwrap_or_else(|err| err.into_inner())
.clone()
}
}
pub(super) fn channel(
shutdown: &CancellationToken,
) -> (EventSender, mpsc::Receiver<DiscoveryEvent>, InboxControl) {
channel_with_capacity(shutdown, EVENT_CAPACITY)
}
fn channel_with_capacity(
shutdown: &CancellationToken,
capacity: usize,
) -> (EventSender, mpsc::Receiver<DiscoveryEvent>, InboxControl) {
let (sender, receiver) = mpsc::sync_channel(capacity);
let state = Arc::new(State::default());
(
EventSender {
sender,
state: state.clone(),
shutdown: shutdown.clone(),
capacity,
},
receiver,
InboxControl { state },
)
}
#[cfg(test)]
pub(super) fn test_channel(
shutdown: &CancellationToken,
) -> (EventSender, mpsc::Receiver<DiscoveryEvent>) {
let (sender, receiver, _) = channel(shutdown);
(sender, receiver)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn overflow_is_bounded_non_blocking_and_cancels_the_producer() {
let shutdown = CancellationToken::new();
let (sender, receiver, control) = channel_with_capacity(&shutdown, 2);
assert!(
sender
.send(DiscoveryEvent::Status("one".to_string()))
.is_ok()
);
assert!(
sender
.send(DiscoveryEvent::Status("two".to_string()))
.is_ok()
);
assert!(
sender
.send(DiscoveryEvent::Status("three".to_string()))
.is_err()
);
assert!(shutdown.is_cancelled());
assert!(
control
.overload_reason()
.unwrap()
.contains("2 pending events")
);
assert_eq!(receiver.try_iter().count(), 2);
}
}