use std::time::Duration;
use time::OffsetDateTime;
use super::outcome::Snapshot;
use super::state::RateLimitState;
pub(super) struct WaitTracker {
started_at: Option<OffsetDateTime>,
total: Option<Duration>,
}
impl WaitTracker {
pub(super) fn new() -> Self {
Self {
started_at: None,
total: None,
}
}
pub(super) fn observe(
&mut self,
now: OffsetDateTime,
remaining: Duration,
) -> (Duration, Duration) {
let started = *self.started_at.get_or_insert(now);
let total = *self.total.get_or_insert(remaining);
let elapsed = (now - started).unsigned_abs();
(elapsed, total)
}
}
pub(super) fn snapshot_from_state(state: &RateLimitState) -> Snapshot {
let format = time::format_description::well_known::Rfc3339;
Snapshot {
next_allowed_at: state.next_allowed_at.format(&format).ok(),
blocked_until: state.blocked_until.and_then(|t| t.format(&format).ok()),
slowdown_until: state.slowdown_until.and_then(|t| t.format(&format).ok()),
}
}
pub(super) fn with_positive_jitter(base: Duration, jitter: Duration) -> Duration {
if jitter.is_zero() {
return base;
}
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| u128::from(d.subsec_nanos()))
.unwrap_or(0);
let pid = u128::from(std::process::id());
let span = jitter.as_nanos().max(1);
let offset = ((nanos.wrapping_mul(2_654_435_761) ^ pid) % span) as u64;
base.saturating_add(Duration::from_nanos(offset))
}