use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex, PoisonError};
use std::time::{Duration, Instant};
use bacnet_types::constructed::BACnetDestination;
use bacnet_types::enums::PropertyIdentifier;
use bacnet_types::error::Error;
use bacnet_types::primitives::{ObjectIdentifier, PropertyValue};
use super::persistence::{ForwarderSnapshot, NotificationForwarderPersistence};
use crate::durable::staged::{StagedSaves, Step};
use crate::durable::{SaveWait, SaveWriter, StageStep};
use crate::subscribed_recipients::SubscribedRecipients;
const RESAVE_INTERVAL: Duration = Duration::from_secs(60);
#[derive(Clone, Debug, Default)]
pub struct ForwarderSaveCounters {
failed_saves: Arc<AtomicU64>,
}
impl ForwarderSaveCounters {
pub fn failed_saves(&self) -> u64 {
self.failed_saves.load(Ordering::Relaxed)
}
fn record_failure(&self) {
#[allow(deprecated, reason = "try_update needs Rust 1.95; the MSRV is 1.93")]
let _ = self
.failed_saves
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |n| {
Some(n.saturating_add(1))
});
}
}
pub(super) enum NextList {
RecipientList(Vec<BACnetDestination>),
SubscribedRecipients(SubscribedRecipients),
}
pub(super) fn apply(snapshot: &mut ForwarderSnapshot, next: &NextList) {
match next {
NextList::RecipientList(list) => snapshot.recipient_list = Some(list.clone()),
NextList::SubscribedRecipients(store) => {
snapshot.subscribed_recipients = store.subscriptions();
}
}
}
pub(super) struct Storage {
saves: StagedSaves<ForwarderSnapshot, NextList>,
confirmed: Arc<Mutex<ForwarderSnapshot>>,
last_attempt: Option<Duration>,
resave_now: bool,
}
impl Storage {
pub(super) fn new(
oid: ObjectIdentifier,
persistence: Arc<dyn NotificationForwarderPersistence>,
confirmed: ForwarderSnapshot,
counters: ForwarderSaveCounters,
) -> Self {
let confirmed = Arc::new(Mutex::new(confirmed));
let saved = Arc::clone(&confirmed);
let writer = SaveWriter::new(
format!("bacnet-nf-{}-save", oid.instance_number()),
move |snapshot: &ForwarderSnapshot| persistence.save(oid, snapshot),
move |snapshot, result| match result {
Ok(()) => {
*saved.lock().unwrap_or_else(PoisonError::into_inner) = snapshot.clone();
}
Err(error) => {
counters.record_failure();
tracing::warn!(
forwarder = %oid,
%error,
"Failed to save Notification Forwarder lists"
);
}
},
);
Self {
saves: StagedSaves::new(writer),
confirmed,
last_attempt: None,
resave_now: false,
}
}
pub(super) fn busy(&mut self) -> Option<SaveWait> {
self.busy_at(Instant::now())
}
pub(super) fn busy_at(&mut self, now: Instant) -> Option<SaveWait> {
self.saves.busy_at(now)
}
pub(super) fn stage(
&mut self,
steps: Vec<Step<NextList>>,
base: u64,
served: ForwarderSnapshot,
) -> StageStep {
self.saves.stage(steps, base, served, apply)
}
pub(super) fn claim(
&mut self,
property: PropertyIdentifier,
value: &PropertyValue,
base: u64,
) -> Option<Result<NextList, Error>> {
self.saves.claim(property, None, value, base)
}
pub(super) fn is_staged(&self) -> bool {
self.saves.is_staged()
}
pub(super) fn drop_staged(&mut self) {
self.saves.drop_staged();
}
pub(super) fn save_now(&mut self, snapshot: ForwarderSnapshot) -> Result<(), Error> {
self.saves.save_now(snapshot)
}
pub(super) fn release(&mut self, wait: &SaveWait) {
self.saves.release(wait);
}
pub(super) fn correct(&mut self, served: impl FnOnce() -> ForwarderSnapshot) {
self.saves.correct(served);
}
pub(super) fn drop_forgotten(
&mut self,
served: impl FnOnce() -> ForwarderSnapshot,
) -> SaveWait {
self.saves.drop_forgotten(served)
}
pub(super) fn keep_current(
&mut self,
now: Duration,
lapsed: bool,
current: impl FnOnce() -> ForwarderSnapshot,
) {
if lapsed {
self.resave_now = true;
}
self.saves.expire(now);
if self.busy().is_some() {
return;
}
self.resave_now |= self.saves.take_correction();
let current = current();
if current
== *self
.confirmed
.lock()
.unwrap_or_else(PoisonError::into_inner)
{
self.resave_now = false;
return;
}
let due = self.resave_now
|| self
.last_attempt
.is_none_or(|at| now.saturating_sub(at) >= RESAVE_INTERVAL);
if !due {
return;
}
self.last_attempt = Some(now);
self.resave_now = false;
self.saves.submit_coalescing(current);
}
pub(super) fn clock_changed(&mut self) {
self.last_attempt = None;
self.saves.drop_staged();
}
pub(super) fn wait_idle(&self) {
self.saves.wait_idle();
}
}