use tokio::{spawn, sync::Mutex, sync::RwLock, task::JoinHandle, task::yield_now, time::sleep};
use crate::LoadBalancer;
use std::{
future::Future,
sync::Arc,
time::{Duration, Instant},
};
pub struct Entry<T>
where
T: Send + Sync + Clone + 'static,
{
pub interval: Duration,
pub last: Mutex<Option<Instant>>,
pub value: T,
}
impl<T> Clone for Entry<T>
where
T: Send + Sync + Clone + 'static,
{
fn clone(&self) -> Self {
Self {
interval: self.interval.clone(),
last: self.last.try_lock().unwrap().clone().into(),
value: self.value.clone(),
}
}
}
#[derive(Clone)]
pub struct Cooldown<T>
where
T: Send + Sync + Clone + 'static,
{
inner: Arc<RwLock<Vec<Entry<T>>>>,
}
impl<T> Cooldown<T>
where
T: Send + Sync + Clone + 'static,
{
pub fn new(entries: Vec<(Duration, T)>) -> Self {
Self {
inner: Arc::new(RwLock::new(
entries
.into_iter()
.map(|(interval, value)| Entry {
interval,
last: None.into(),
value,
})
.collect(),
)),
}
}
pub async fn update<F, R, N>(&self, handler: F) -> anyhow::Result<N>
where
F: Fn(Arc<RwLock<Vec<Entry<T>>>>) -> R,
R: Future<Output = anyhow::Result<N>>,
{
handler(self.inner.clone()).await
}
pub async fn update_timer<F, R>(
&self,
handler: F,
interval: Duration,
) -> anyhow::Result<JoinHandle<()>>
where
F: Fn(Arc<RwLock<Vec<Entry<T>>>>) -> R + Send + Sync + 'static,
R: Future<Output = anyhow::Result<()>> + Send,
{
handler(self.inner.clone()).await?;
let inner = self.inner.clone();
Ok(spawn(async move {
loop {
sleep(interval).await;
let _ = handler(inner.clone()).await;
}
}))
}
}
impl<T> LoadBalancer<T> for Cooldown<T>
where
T: Send + Sync + Clone + 'static,
{
fn alloc(&self) -> impl Future<Output = T> + Send {
async move {
loop {
if let Some(v) = LoadBalancer::try_alloc(self) {
return v;
}
let min_remaining = {
let entries = self.inner.read().await;
let mut min = None;
for entry in entries.iter() {
if entry.interval == Duration::ZERO {
continue;
}
if let Some(last_time) = *entry.last.lock().await {
let now = Instant::now();
let elapsed = now.duration_since(last_time);
if elapsed < entry.interval {
let remaining = entry.interval - elapsed;
if min.is_none() || remaining < min.unwrap() {
min = Some(remaining);
}
}
}
}
min
};
if let Some(duration) = min_remaining {
tokio::time::sleep(duration).await;
} else {
yield_now().await;
}
}
}
}
fn try_alloc(&self) -> Option<T> {
let entries = self.inner.try_read().ok()?;
for entry in entries.iter() {
if entry.interval == Duration::ZERO {
return Some(entry.value.clone());
}
if let Ok(mut last) = entry.last.try_lock() {
match *last {
Some(v) => {
let now = Instant::now();
if now.duration_since(v) >= entry.interval {
*last = Some(now);
return Some(entry.value.clone());
}
}
None => {
*last = Some(Instant::now());
return Some(entry.value.clone());
}
}
}
}
None
}
}