use std::collections::VecDeque;
use std::sync::{Arc, PoisonError, RwLock, RwLockReadGuard};
use std::time::{Duration, Instant, SystemTime};
use zeroize::Zeroizing;
use super::BrokerError;
const KEPT: usize = 4;
pub(super) struct Slot {
current: Zeroizing<String>,
until: Option<(Instant, SystemTime)>,
replaced: VecDeque<Zeroizing<String>>,
}
impl Slot {
pub(super) fn fixed(value: Zeroizing<String>) -> Arc<RwLock<Self>> {
Arc::new(RwLock::new(Self {
current: value,
until: None,
replaced: VecDeque::new(),
}))
}
pub(super) fn live(&self) -> Option<&Zeroizing<String>> {
match self.until {
Some((mono, wall)) if Instant::now() >= mono || SystemTime::now() >= wall => None,
_ => Some(&self.current),
}
}
pub(super) fn known(&self) -> impl Iterator<Item = &Zeroizing<String>> {
std::iter::once(&self.current).chain(&self.replaced)
}
}
pub(super) fn read(slot: &RwLock<Slot>) -> RwLockReadGuard<'_, Slot> {
slot.read().unwrap_or_else(PoisonError::into_inner)
}
#[derive(Clone)]
pub struct Rotation {
id: String,
slot: Arc<RwLock<Slot>>,
}
impl std::fmt::Debug for Rotation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Rotation")
.field("id", &self.id)
.finish_non_exhaustive()
}
}
impl Rotation {
pub(super) fn new(id: &str, slot: &Arc<RwLock<Slot>>) -> Self {
Self {
id: id.to_owned(),
slot: Arc::clone(slot),
}
}
pub fn renew(&self, value: Zeroizing<String>, lifetime: Duration) -> Result<(), BrokerError> {
super::usable(&self.id, &value)?;
let until = (
Instant::now() + lifetime,
SystemTime::now()
.checked_add(lifetime)
.unwrap_or(SystemTime::UNIX_EPOCH),
);
let mut slot = self.slot.write().unwrap_or_else(PoisonError::into_inner);
if *slot.current != *value {
let old = std::mem::replace(&mut slot.current, value);
slot.replaced.push_front(old);
slot.replaced.truncate(KEPT);
}
slot.until = Some(until);
Ok(())
}
#[must_use]
pub fn remaining(&self) -> Option<Duration> {
let slot = read(&self.slot);
let (mono, wall) = slot.until?;
let by_wall = wall
.duration_since(SystemTime::now())
.unwrap_or(Duration::ZERO);
Some(mono.saturating_duration_since(Instant::now()).min(by_wall))
}
}
#[cfg(test)]
mod tests;