use std::{
collections::VecDeque,
sync::{Arc, Mutex, MutexGuard},
};
use mdns_proto::{CollectedAnswer, QueryHandle, QueryUpdate};
use crate::{command::Command, error::CancelError};
const MAX_QUERY_EVENT_BACKLOG: usize = 1024;
#[derive(Debug, Clone)]
pub enum QueryEvent {
Answer(CollectedAnswer),
Terminal(QueryUpdate),
}
pub(crate) struct QueryMailbox {
answers: VecDeque<CollectedAnswer>,
terminal: Option<QueryUpdate>,
terminal_delivered: bool,
dropped: u64,
}
enum Drained {
Event(QueryEvent),
Ended,
Empty,
}
impl QueryMailbox {
fn new() -> Self {
Self {
answers: VecDeque::new(),
terminal: None,
terminal_delivered: false,
dropped: 0,
}
}
pub(crate) fn push_answer(&mut self, ans: CollectedAnswer) {
if let Some(slot) = self.answers.iter_mut().find(|a| {
a.rtype() == ans.rtype() && a.rclass() == ans.rclass() && a.rdata_key() == ans.rdata_key()
}) {
*slot = ans;
return;
}
if self.answers.len() >= MAX_QUERY_EVENT_BACKLOG && self.answers.pop_front().is_some() {
self.dropped = self.dropped.saturating_add(1);
}
self.answers.push_back(ans);
}
pub(crate) fn record_dropped(&mut self, n: u64) {
self.dropped = self.dropped.saturating_add(n);
}
pub(crate) fn set_terminal(&mut self, terminal: QueryUpdate) {
if self.terminal.is_none() && !self.terminal_delivered {
self.terminal = Some(terminal);
}
}
fn drain(&mut self) -> Drained {
if let Some(ans) = self.answers.pop_front() {
Drained::Event(QueryEvent::Answer(ans))
} else if let Some(terminal) = self.terminal.take() {
self.terminal_delivered = true;
Drained::Event(QueryEvent::Terminal(terminal))
} else if self.terminal_delivered {
Drained::Ended
} else {
Drained::Empty
}
}
}
fn lock(mailbox: &Mutex<QueryMailbox>) -> MutexGuard<'_, QueryMailbox> {
mailbox
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
pub struct Query {
handle: QueryHandle,
mailbox: Arc<Mutex<QueryMailbox>>,
doorbell: async_channel::Receiver<()>,
cmd: async_channel::Sender<Command>,
}
impl Query {
pub(crate) fn new(
handle: QueryHandle,
mailbox: Arc<Mutex<QueryMailbox>>,
doorbell: async_channel::Receiver<()>,
cmd: async_channel::Sender<Command>,
) -> Self {
Self {
handle,
mailbox,
doorbell,
cmd,
}
}
#[inline]
pub const fn handle(&self) -> QueryHandle {
self.handle
}
pub fn dropped_answers(&self) -> u64 {
lock(&self.mailbox).dropped
}
pub(crate) fn dropped_handle(&self) -> DroppedHandle {
DroppedHandle {
mailbox: Arc::clone(&self.mailbox),
}
}
pub async fn next(&mut self) -> Option<QueryEvent> {
loop {
match lock(&self.mailbox).drain() {
Drained::Event(ev) => return Some(ev),
Drained::Ended => return None,
Drained::Empty => {}
}
if self.doorbell.recv().await.is_err() {
return match lock(&self.mailbox).drain() {
Drained::Event(ev) => Some(ev),
_ => None,
};
}
}
}
pub async fn cancel(self) -> Result<(), CancelError> {
self
.cmd
.send(Command::CancelQuery {
handle: self.handle,
})
.await
.map_err(|_| CancelError::DriverGone)?;
Ok(())
}
}
impl Drop for Query {
fn drop(&mut self) {
let _ = self.cmd.try_send(Command::CancelQuery {
handle: self.handle,
});
}
}
pub(crate) struct DroppedHandle {
mailbox: Arc<Mutex<QueryMailbox>>,
}
impl DroppedHandle {
pub(crate) fn get(&self) -> u64 {
lock(&self.mailbox).dropped
}
}
pub(crate) fn new_mailbox() -> (
Arc<Mutex<QueryMailbox>>,
async_channel::Sender<()>,
async_channel::Receiver<()>,
) {
let mailbox = Arc::new(Mutex::new(QueryMailbox::new()));
let (doorbell_tx, doorbell_rx) = async_channel::bounded(1);
(mailbox, doorbell_tx, doorbell_rx)
}
#[cfg(test)]
mod tests;