use core::hash::Hash;
use std::collections::HashMap;
use std::time::{SystemTime, UNIX_EPOCH};
pub struct Throttle {
interval_duration: u128,
max_hits_in_interval: u64,
lockout_duration: u128,
counter: Counter,
}
pub struct Counter {
interval_start: u128,
current_hit_counter: u64,
locked_until: u128,
}
impl Throttle {
pub fn new(interval: u128, max_hits: u64, lockout_duration: u128) -> Throttle {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis();
Throttle {
interval_duration: interval,
max_hits_in_interval: max_hits,
lockout_duration: lockout_duration,
counter: Counter {
interval_start: now,
current_hit_counter: 0,
locked_until: 0,
},
}
}
pub fn is_throttled(&mut self) -> bool {
self.counter.current_hit_counter += 1;
let mut now: u128 = 0;
if self.counter.locked_until != 0 {
now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis();
if self.counter.locked_until > now {
return true;
}
self.counter.locked_until = 0;
}
if self.counter.current_hit_counter <= self.max_hits_in_interval {
return false;
}
if now == 0 {
now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis();
}
if self.counter.locked_until > 0 {
if self.counter.locked_until > now {
return true;
}
self.counter.interval_start = now;
self.counter.locked_until = 0;
self.counter.current_hit_counter = 1;
return false;
}
if now - self.counter.interval_start <= self.interval_duration {
self.counter.interval_start = now;
self.counter.current_hit_counter = 1;
self.counter.locked_until = now + self.lockout_duration;
return true;
}
self.counter.interval_start = now;
self.counter.current_hit_counter = 1;
return false;
}
}
pub struct ThrottleHash<H: Eq + Hash + Clone> {
interval_duration: u128,
max_hits_in_interval: u64,
lockout_duration: u128,
counters: HashMap<H, Counter>,
}
impl<H: Eq + Hash + Clone> ThrottleHash<H> {
pub fn new(interval: u128, max_hits: u64, lockout_duration: u128) -> Self {
ThrottleHash {
interval_duration: interval,
max_hits_in_interval: max_hits,
lockout_duration: lockout_duration,
counters: HashMap::<H, Counter>::new(),
}
}
pub fn is_throttled(&mut self, key: &H) -> bool {
let counter = match self.counters.get_mut(key) {
Some(c) => c,
None => {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis();
let c = Counter {
interval_start: now,
locked_until: 0,
current_hit_counter: 0,
};
self.counters.insert(key.clone(), c);
self.counters.get_mut(key).unwrap()
}
};
counter.current_hit_counter += 1;
let mut now: u128 = 0;
if counter.locked_until != 0 {
now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis();
if counter.locked_until > now {
return true;
}
counter.locked_until = 0;
}
if counter.current_hit_counter <= self.max_hits_in_interval {
return false;
}
if now == 0 {
now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis();
}
if counter.locked_until > 0 {
if counter.locked_until > now {
return true;
}
counter.interval_start = now;
counter.locked_until = 0;
counter.current_hit_counter = 1;
return false;
}
if now - counter.interval_start <= self.interval_duration {
counter.interval_start = now;
counter.current_hit_counter = 1;
counter.locked_until = now + self.lockout_duration;
return true;
}
counter.interval_start = now;
counter.current_hit_counter = 1;
return false;
}
}
#[cfg(test)]
mod tests {
use crate::Throttle;
use crate::ThrottleHash;
use std::thread;
use std::time::Duration;
#[test]
fn test_throttle() {
let mut t = Throttle::new(500, 3, 1000);
assert!(!t.is_throttled());
thread::sleep(Duration::from_millis(600));
assert!(!t.is_throttled());
thread::sleep(Duration::from_millis(600));
assert!(!t.is_throttled());
thread::sleep(Duration::from_millis(600));
assert!(!t.is_throttled());
thread::sleep(Duration::from_millis(600));
assert!(!t.is_throttled());
thread::sleep(Duration::from_millis(600));
assert!(!t.is_throttled());
assert!(!t.is_throttled());
assert!(!t.is_throttled());
assert!(!t.is_throttled());
assert!(t.is_throttled()); thread::sleep(Duration::from_millis(300));
assert!(t.is_throttled());
thread::sleep(Duration::from_millis(300));
assert!(t.is_throttled());
thread::sleep(Duration::from_millis(500));
assert!(!t.is_throttled());
assert!(!t.is_throttled());
assert!(!t.is_throttled());
assert!(t.is_throttled());
thread::sleep(Duration::from_millis(1100));
assert!(!t.is_throttled());
}
#[test]
fn test_throttle_key() {
let mut t = ThrottleHash::<String>::new(500, 3, 1000);
let email1 = "bob1@example.com".to_string();
assert!(!t.is_throttled(&email1));
thread::sleep(Duration::from_millis(600));
assert!(!t.is_throttled(&email1));
thread::sleep(Duration::from_millis(600));
assert!(!t.is_throttled(&email1));
thread::sleep(Duration::from_millis(600));
assert!(!t.is_throttled(&email1));
thread::sleep(Duration::from_millis(600));
assert!(!t.is_throttled(&email1));
thread::sleep(Duration::from_millis(600));
assert!(!t.is_throttled(&email1));
assert!(!t.is_throttled(&email1));
assert!(!t.is_throttled(&email1));
assert!(!t.is_throttled(&email1));
assert!(t.is_throttled(&email1));
thread::sleep(Duration::from_millis(300));
assert!(t.is_throttled(&email1));
thread::sleep(Duration::from_millis(300));
assert!(t.is_throttled(&email1));
thread::sleep(Duration::from_millis(500));
assert!(!t.is_throttled(&email1));
assert!(!t.is_throttled(&email1));
assert!(!t.is_throttled(&email1));
assert!(t.is_throttled(&email1));
thread::sleep(Duration::from_millis(1100));
assert!(!t.is_throttled(&email1));
}
#[test]
fn test_throttle_key_overlap() {
let mut t = ThrottleHash::<String>::new(500, 3, 1000);
let email1 = "bob1@example.com".to_string();
let email2 = "bob2@example.com".to_string();
assert!(!t.is_throttled(&email1));
assert!(!t.is_throttled(&email2));
thread::sleep(Duration::from_millis(600));
assert!(!t.is_throttled(&email1));
assert!(!t.is_throttled(&email2));
thread::sleep(Duration::from_millis(600));
assert!(!t.is_throttled(&email1));
assert!(!t.is_throttled(&email2));
thread::sleep(Duration::from_millis(600));
assert!(!t.is_throttled(&email1));
assert!(!t.is_throttled(&email2));
thread::sleep(Duration::from_millis(600));
assert!(!t.is_throttled(&email1));
assert!(!t.is_throttled(&email2));
thread::sleep(Duration::from_millis(600));
assert!(!t.is_throttled(&email1));
assert!(!t.is_throttled(&email2));
assert!(!t.is_throttled(&email1));
assert!(!t.is_throttled(&email2));
assert!(!t.is_throttled(&email1));
assert!(!t.is_throttled(&email2));
assert!(!t.is_throttled(&email1));
assert!(!t.is_throttled(&email2));
assert!(t.is_throttled(&email1));
assert!(t.is_throttled(&email2));
thread::sleep(Duration::from_millis(300));
assert!(t.is_throttled(&email1));
assert!(t.is_throttled(&email2));
thread::sleep(Duration::from_millis(300));
assert!(t.is_throttled(&email1));
assert!(t.is_throttled(&email2));
thread::sleep(Duration::from_millis(500));
assert!(!t.is_throttled(&email1));
assert!(!t.is_throttled(&email2));
assert!(!t.is_throttled(&email1));
assert!(!t.is_throttled(&email2));
assert!(!t.is_throttled(&email1));
assert!(!t.is_throttled(&email2));
assert!(t.is_throttled(&email1));
assert!(t.is_throttled(&email2));
thread::sleep(Duration::from_millis(1100));
assert!(!t.is_throttled(&email1));
assert!(!t.is_throttled(&email2));
}
}