use crate::core::health::HealthMonitor;
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
pub struct BackendPool {
pub addresses: Vec<String>,
counter: AtomicUsize,
}
pub struct LoadBalancer {
routes: HashMap<String, BackendPool>,
health: Arc<HealthMonitor>,
}
impl LoadBalancer {
#[must_use]
pub fn new(routes: HashMap<String, Vec<String>>, health: Arc<HealthMonitor>) -> Self {
let mut pools = HashMap::new();
for (key, addrs) in routes {
pools.insert(
key,
BackendPool {
addresses: addrs,
counter: AtomicUsize::new(0),
},
);
}
Self {
routes: pools,
health,
}
}
pub async fn next_available(&self, protocol: &str, metadata: Option<&str>) -> Option<&String> {
if let Some(m) = metadata {
let specific_key = format!("{protocol}:{m}");
if let Some(pool) = self.routes.get(&specific_key) {
let res = self.pick_from_pool(pool).await;
if res.is_some() {
return res;
}
}
}
if let Some(pool) = self.routes.get(protocol) {
return self.pick_from_pool(pool).await;
}
None
}
async fn pick_from_pool<'a>(&self, pool: &'a BackendPool) -> Option<&'a String> {
let len = pool.addresses.len();
if len == 0 {
return None;
}
for _ in 0..len {
let idx = pool.counter.fetch_add(1, Ordering::Relaxed) % len;
let addr = &pool.addresses[idx];
if self.health.is_healthy(addr).await {
return Some(addr);
}
}
None
}
pub async fn get_status(&self) -> HashMap<String, Vec<(String, bool)>> {
let mut status = HashMap::new();
for (key, pool) in &self.routes {
let mut backends = Vec::new();
for addr in &pool.addresses {
backends.push((addr.clone(), self.health.is_healthy(addr).await));
}
status.insert(key.clone(), backends);
}
status
}
}