use crate::{conn::H2Pooled, h3::H3PoolEntry, pool::WeakPool};
use std::{fmt::Debug, hash::Hash, time::Duration};
use trillium_server_common::{Runtime, Transport, url::Origin};
const REAPER_INTERVAL: Duration = Duration::from_secs(300);
pub(crate) fn spawn_pool_reaper(
runtime: Runtime,
h1_pool: WeakPool<Origin, Box<dyn Transport>>,
h2_pool: WeakPool<Origin, H2Pooled>,
h3_pool: Option<WeakPool<Origin, H3PoolEntry>>,
) {
runtime
.clone()
.spawn(reap_loop(runtime.clone(), REAPER_INTERVAL, h1_pool));
runtime
.clone()
.spawn(reap_loop(runtime.clone(), REAPER_INTERVAL, h2_pool));
if let Some(h3_pool) = h3_pool {
runtime
.clone()
.spawn(reap_loop(runtime, REAPER_INTERVAL, h3_pool));
}
}
async fn reap_loop<K, V>(runtime: Runtime, interval: Duration, weak: WeakPool<K, V>)
where
K: Hash + Debug + Eq + Clone,
{
loop {
runtime.delay(interval).await;
match weak.upgrade() {
Some(pool) => pool.reap(),
None => return,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Pool, pool::PoolEntry};
use std::time::Instant;
use trillium_testing::{harness, test};
#[test(harness)]
async fn reaps_untouched_origin_then_exits_when_pool_drops() {
let runtime = Runtime::new(trillium_testing::runtime());
let pool: Pool<String, u8> = Pool::default();
pool.insert(
"origin".into(),
PoolEntry::new(1, Some(Instant::now() - Duration::from_secs(1))),
);
assert_eq!(pool.keys().count(), 1);
let weak = pool.downgrade();
runtime.clone().spawn(reap_loop(
runtime.clone(),
Duration::from_millis(2),
weak.clone(),
));
for _ in 0..500 {
if weak.upgrade().is_some_and(|p| p.keys().count() == 0) {
break;
}
runtime.delay(Duration::from_millis(1)).await;
}
assert_eq!(
pool.keys().count(),
0,
"reaper should drop the expired entry of an origin nothing came back to pop"
);
drop(pool);
assert!(
weak.upgrade().is_none(),
"reaper no longer keeps the pool alive"
);
}
}