Merge pull request 'Enh RateLimiters with Async Sleep' (#25) from chat_async_sleep into main
All checks were successful
ci/woodpecker/push/cargo-checks Pipeline was successful

Reviewed-on: #25
This commit is contained in:
modulatingforce 2024-03-18 22:35:07 -04:00
commit 53c5780322
3 changed files with 50 additions and 6 deletions

View file

@ -19,6 +19,8 @@ use crate::core::botinstance::ChType;
use crate::core::botlog; use crate::core::botlog;
pub use ChType::Channel; pub use ChType::Channel;
use tokio::time::{sleep, Duration};
#[derive(Clone)] #[derive(Clone)]
pub struct Chat { pub struct Chat {
pub ratelimiters: Arc<Mutex<HashMap<ChType, RateLimiter>>>, // used to limit messages sent per channel pub ratelimiters: Arc<Mutex<HashMap<ChType, RateLimiter>>>, // used to limit messages sent per channel
@ -59,6 +61,11 @@ impl Chat {
.get_mut(&Channel(String::from(&msg.channel_login))) .get_mut(&Channel(String::from(&msg.channel_login)))
.expect("ERROR: Issue with Rate limiters"); .expect("ERROR: Issue with Rate limiters");
// Continue to check the limiter and sleep if required if the minimum is not reached
while let ratelimiter::LimiterResp::Sleep(sleeptime) = contextratelimiter.check_limiter() {
sleep(Duration::from_secs_f64(sleeptime)).await;
}
match contextratelimiter.check_limiter() { match contextratelimiter.check_limiter() {
ratelimiter::LimiterResp::Allow => { ratelimiter::LimiterResp::Allow => {
let maxblanks = rand::thread_rng().gen_range(1..=20); let maxblanks = rand::thread_rng().gen_range(1..=20);
@ -73,8 +80,8 @@ impl Chat {
contextratelimiter.increment_counter(); contextratelimiter.increment_counter();
let logstr = format!( let logstr = format!(
"(#{}) > {} ; Ratelimiers : {:?}", "(#{}) > {} ; contextratelimiter : {:?}",
msg.channel_login, "rate limit counter increase", self.ratelimiters msg.channel_login, "rate limit counter increase", contextratelimiter
); );
botlog::trace( botlog::trace(
@ -86,7 +93,11 @@ impl Chat {
ratelimiter::LimiterResp::Skip => { ratelimiter::LimiterResp::Skip => {
// (); // do nothing otherwise // (); // do nothing otherwise
} }
ratelimiter::LimiterResp::Sleep(_) => {
panic!("ISSUE : sleep was already awaited - Should not happen?");
} }
}
Log::flush(); Log::flush();
} }

View file

@ -1,18 +1,23 @@
const TIME_THRESHOLD_S: u64 = 30; const TIME_THRESHOLD_S: u64 = 30;
const TIME_MIN_S_F64: f64 = 1.0;
const MSG_THRESHOLD: u32 = 20; const MSG_THRESHOLD: u32 = 20;
use std::time::Instant; use std::time::Instant;
use crate::core::botlog;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct RateLimiter { pub struct RateLimiter {
timer: Instant, timer: Instant,
msgcounter: u32, msgcounter: u32,
lastmsgtimer : Instant,
} }
#[derive(Debug)]
pub enum LimiterResp { pub enum LimiterResp {
Allow, // when it's evaluated to be within limits Allow, // when it's evaluated to be within limits
Skip, // as outside of rate limits Skip, // as outside of rate limits
// Enqueue, // [FUTURE] // Enqueue, // [FUTURE]
Sleep(f64), // Sleep for x seconds
} }
impl Default for RateLimiter { impl Default for RateLimiter {
@ -26,23 +31,50 @@ impl RateLimiter {
Self { Self {
timer: Instant::now(), timer: Instant::now(),
msgcounter: 0, msgcounter: 0,
lastmsgtimer: Instant::now(),
} }
} }
pub fn check_limiter(&mut self) -> LimiterResp { pub fn check_limiter(&mut self) -> LimiterResp {
if self.timer.elapsed().as_secs() >= TIME_THRESHOLD_S {
let logstr = format!(
">> RateLimiter > {:?}",self
);
botlog::trace(
logstr.as_str(),
Some("Rate Limiter Inner".to_string()),
None,
);
let rsp = if self.timer.elapsed().as_secs() >= TIME_THRESHOLD_S {
self.timer = Instant::now(); self.timer = Instant::now();
self.msgcounter = 0; self.msgcounter = 0;
LimiterResp::Allow LimiterResp::Allow
} else if self.msgcounter < MSG_THRESHOLD { } else if self.msgcounter < MSG_THRESHOLD &&
self.lastmsgtimer.elapsed().as_secs_f64() >= TIME_MIN_S_F64 {
LimiterResp::Allow LimiterResp::Allow
} else { } else {
// when elapsed() < TIME_THRESHOLD_S && msgcounter >= MSG_THRESHOLD // when elapsed() < TIME_THRESHOLD_S && msgcounter >= MSG_THRESHOLD
LimiterResp::Skip // LimiterResp::Skip
} LimiterResp::Sleep(TIME_MIN_S_F64 - self.lastmsgtimer.elapsed().as_secs_f64())
};
botlog::trace(
&format!("Limiter Response : {:?} ; Elapsed (as_sec_f64) : {}",
rsp, self.lastmsgtimer.elapsed().as_secs_f64()),
Some("Rate Limiter Inner".to_string()),
None,
);
rsp
} }
pub fn increment_counter(&mut self) { pub fn increment_counter(&mut self) {
self.msgcounter += 1; self.msgcounter += 1;
self.lastmsgtimer = Instant::now();
} }
} }

View file

@ -66,6 +66,7 @@ async fn good_girl(bot: BotAR, msg: PrivmsgMessage) {
if msg.sender.name.to_lowercase() == "ModulatingForce".to_lowercase() if msg.sender.name.to_lowercase() == "ModulatingForce".to_lowercase()
|| msg.sender.name.to_lowercase() == "mzNToRi".to_lowercase() || msg.sender.name.to_lowercase() == "mzNToRi".to_lowercase()
// if msg.sender.name.to_lowercase() == "mzNToRi".to_lowercase()
{ {
botlog::debug( botlog::debug(
"Good Girl Detected > Pausechamp", "Good Girl Detected > Pausechamp",