use bacnet_transport::warn_throttle::WarnThrottle;
use bacnet_types::error::Error;
use tracing::{debug, warn};
#[derive(Debug, Default)]
pub(super) struct SendWarnings {
unicast: WarnThrottle,
broadcast: WarnThrottle,
}
impl SendWarnings {
pub(super) fn unicast_failed(&self, error: &Error) -> Option<u64> {
failed(&self.unicast, "unicast", error)
}
pub(super) fn broadcast_failed(&self, error: &Error) -> Option<u64> {
failed(&self.broadcast, "broadcast", error)
}
}
fn failed(throttle: &WarnThrottle, send: &'static str, error: &Error) -> Option<u64> {
let failed = throttle.note();
match failed {
Some(failed) => warn!(
%error,
send,
failed,
"Router send failed (failures since the last warning)"
),
None => debug!(%error, send, "Router send failed"),
}
failed
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use bacnet_transport::warn_throttle::WARNING_INTERVAL;
use super::*;
#[tokio::test(start_paused = true)]
async fn a_burst_of_failed_sends_warns_once_per_interval() {
let warnings = SendWarnings::default();
let error = Error::Encoding("refused".into());
assert_eq!(warnings.unicast_failed(&error), Some(1));
for _ in 0..5 {
assert_eq!(warnings.unicast_failed(&error), None);
}
assert_eq!(warnings.broadcast_failed(&error), Some(1));
tokio::time::advance(WARNING_INTERVAL - Duration::from_millis(1)).await;
assert_eq!(warnings.unicast_failed(&error), None);
tokio::time::advance(Duration::from_millis(1)).await;
assert_eq!(warnings.unicast_failed(&error), Some(7));
}
}