use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, Instant};
#[derive(Debug)]
pub struct RateLimiter {
limit: u64,
count: AtomicU64,
last_reset_ns: AtomicU64,
epoch: Instant,
}
impl RateLimiter {
pub const DEFAULT_LIMIT: u64 = 10;
pub const RESET_PERIOD: Duration = Duration::from_secs(1);
#[must_use]
pub fn new(limit: u64) -> Self {
Self {
limit,
count: AtomicU64::new(0),
last_reset_ns: AtomicU64::new(0),
epoch: Instant::now(),
}
}
#[must_use]
pub fn note_handshake(&self) -> bool {
self.count.fetch_add(1, Ordering::Relaxed) >= self.limit
}
#[must_use]
pub fn is_under_load(&self) -> bool {
self.count.load(Ordering::Relaxed) >= self.limit
}
pub fn maybe_reset(&self) {
let now_ns = u64::try_from(self.epoch.elapsed().as_nanos()).unwrap_or(u64::MAX);
let last = self.last_reset_ns.load(Ordering::Relaxed);
let period = u64::try_from(Self::RESET_PERIOD.as_nanos()).unwrap_or(u64::MAX);
if now_ns.saturating_sub(last) >= period
&& self
.last_reset_ns
.compare_exchange(last, now_ns, Ordering::AcqRel, Ordering::Relaxed)
.is_ok()
{
self.count.store(0, Ordering::Relaxed);
}
}
pub fn reset(&self) {
self.count.store(0, Ordering::Relaxed);
let now_ns = u64::try_from(self.epoch.elapsed().as_nanos()).unwrap_or(u64::MAX);
self.last_reset_ns.store(now_ns, Ordering::Relaxed);
}
}
impl Default for RateLimiter {
fn default() -> Self {
Self::new(Self::DEFAULT_LIMIT)
}
}