use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use arc_swap::ArcSwap;
use brain_db_sdk::{Auth, BrainClient, BrainError, Pool};
use tokio::sync::OnceCell;
use tracing::{info, warn};
#[derive(Clone, Debug)]
pub struct BrainPoolConfig {
pub brain_addr: SocketAddr,
pub pool_size: usize,
pub max_credentials: usize,
pub idle_ttl: Duration,
}
struct PoolEntry {
pool: Arc<Pool>,
last_used_secs: AtomicU64,
}
fn now_secs() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_or(0, |d| d.as_secs())
}
fn lru_victim_key<'a>(
recencies: impl Iterator<Item = (&'a str, u64)>,
len: usize,
cap: usize,
) -> Option<String> {
if len < cap {
return None;
}
recencies
.min_by_key(|&(_, last_used)| last_used)
.map(|(key, _)| key.to_string())
}
fn stale_keys<'a>(recencies: impl Iterator<Item = (&'a str, u64)>, cutoff: u64) -> Vec<String> {
recencies
.filter(|&(_, last_used)| last_used < cutoff)
.map(|(key, _)| key.to_string())
.collect()
}
enum Mode {
PerCredential(ArcSwap<HashMap<String, Arc<PoolEntry>>>),
Shared {
service_credential: String,
pool: OnceCell<Arc<Pool>>,
},
}
pub struct BrainPool {
config: BrainPoolConfig,
mode: Mode,
}
impl std::fmt::Debug for BrainPool {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BrainPool")
.field(
"mode",
&match self.mode {
Mode::PerCredential(_) => "per-credential",
Mode::Shared { .. } => "shared",
},
)
.field("max_credentials", &self.config.max_credentials)
.finish_non_exhaustive()
}
}
impl BrainPool {
#[must_use]
pub fn new(config: BrainPoolConfig) -> Self {
let config = BrainPoolConfig {
max_credentials: config.max_credentials.max(1),
..config
};
Self {
config,
mode: Mode::PerCredential(ArcSwap::from_pointee(HashMap::new())),
}
}
#[must_use]
pub fn shared(config: BrainPoolConfig, service_credential: String) -> Self {
Self {
config,
mode: Mode::Shared {
service_credential,
pool: OnceCell::new(),
},
}
}
pub async fn client_for(&self, credential: &str) -> Result<Arc<BrainClient>, BrainError> {
match &self.mode {
Mode::PerCredential(pools) => self.client_for_credential(pools, credential).await,
Mode::Shared {
service_credential,
pool,
} => {
let shared = pool
.get_or_try_init(|| async {
let auth = Auth::Token(service_credential.as_bytes().to_vec());
Pool::connect(self.config.brain_addr, self.config.pool_size, auth)
.await
.map(Arc::new)
.map_err(|e| {
warn!(error = %e, "brain shared service pool connect failed");
e
})
})
.await?;
shared.get_healthy().await
}
}
}
async fn client_for_credential(
&self,
pools: &ArcSwap<HashMap<String, Arc<PoolEntry>>>,
credential: &str,
) -> Result<Arc<BrainClient>, BrainError> {
let hit = pools.load().get(credential).map(Arc::clone);
if let Some(entry) = hit {
entry.last_used_secs.store(now_secs(), Ordering::Relaxed);
return entry.pool.get_healthy().await;
}
let auth = Auth::Token(credential.as_bytes().to_vec());
let built = Pool::connect(self.config.brain_addr, self.config.pool_size, auth)
.await
.map_err(|e| {
warn!(error = %e, "brain pool connect failed");
e
})?;
let entry = Arc::new(PoolEntry {
pool: Arc::new(built),
last_used_secs: AtomicU64::new(now_secs()),
});
pools.rcu(|current| {
if current.contains_key(credential) {
Arc::clone(current)
} else {
let mut next = HashMap::clone(current);
if let Some(lru_key) = lru_victim_key(
next.iter()
.map(|(k, e)| (k.as_str(), e.last_used_secs.load(Ordering::Relaxed))),
next.len(),
self.config.max_credentials,
) {
next.remove(&lru_key);
}
next.insert(credential.to_string(), Arc::clone(&entry));
Arc::new(next)
}
});
let entry = pools
.load()
.get(credential)
.map(Arc::clone)
.expect("invariant: pool for credential is present after rcu insert");
entry.pool.get_healthy().await
}
pub fn sweep_idle(&self) {
let Mode::PerCredential(pools) = &self.mode else {
return;
};
let cutoff = now_secs().saturating_sub(self.config.idle_ttl.as_secs());
let current = pools.load();
let evicted = stale_keys(
current
.iter()
.map(|(k, e)| (k.as_str(), e.last_used_secs.load(Ordering::Relaxed))),
cutoff,
);
if evicted.is_empty() {
return;
}
pools.rcu(|current| {
let mut next = HashMap::clone(current);
for key in &evicted {
next.remove(key);
}
Arc::new(next)
});
info!(count = evicted.len(), "swept idle brain credential pools");
}
pub async fn probe(&self, timeout: Duration) -> bool {
matches!(
tokio::time::timeout(
timeout,
tokio::net::TcpStream::connect(self.config.brain_addr),
)
.await,
Ok(Ok(_))
)
}
#[cfg(test)]
fn cached_len(&self) -> usize {
match &self.mode {
Mode::PerCredential(pools) => pools.load().len(),
Mode::Shared { .. } => 0,
}
}
#[cfg(test)]
fn shared_service_credential(&self) -> Option<&str> {
match &self.mode {
Mode::Shared {
service_credential, ..
} => Some(service_credential),
Mode::PerCredential(_) => None,
}
}
#[cfg(test)]
fn shared_pool_initialized(&self) -> Option<bool> {
match &self.mode {
Mode::Shared { pool, .. } => Some(pool.initialized()),
Mode::PerCredential(_) => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_config(max_credentials: usize, idle_ttl: Duration) -> BrainPoolConfig {
BrainPoolConfig {
brain_addr: "127.0.0.1:1".parse().expect("addr"),
pool_size: 1,
max_credentials,
idle_ttl,
}
}
#[test]
fn max_credentials_is_floored_at_one() {
let pool = BrainPool::new(test_config(0, Duration::from_secs(900)));
assert_eq!(pool.config.max_credentials, 1);
}
#[test]
fn a_fresh_pool_caches_nothing() {
let pool = BrainPool::new(test_config(8, Duration::from_secs(900)));
assert_eq!(pool.cached_len(), 0);
}
#[test]
fn lru_victim_is_none_below_the_cap() {
let recencies = [("a", 100u64), ("b", 200)];
assert_eq!(
lru_victim_key(recencies.iter().copied(), recencies.len(), 4),
None
);
}
#[test]
fn lru_victim_picks_the_stalest_at_the_cap() {
let recencies = [("a", 100u64), ("b", 300), ("c", 200)];
assert_eq!(
lru_victim_key(recencies.iter().copied(), recencies.len(), 2),
Some("a".to_string()),
"stalest entry ('a', oldest last-used) is the eviction victim",
);
}
#[test]
fn stale_keys_selects_only_entries_past_the_cutoff() {
let recencies = [
("old1", 100u64),
("fresh1", 300),
("old2", 200),
("fresh2", 400),
];
let mut stale = stale_keys(recencies.iter().copied(), 250);
stale.sort();
assert_eq!(stale, vec!["old1".to_string(), "old2".to_string()]);
}
#[test]
fn stale_keys_is_empty_when_all_are_fresh() {
let recencies = [("a", 300u64), ("b", 400)];
assert_eq!(
stale_keys(recencies.iter().copied(), 250),
[] as [String; 0]
);
}
#[test]
fn sweep_idle_on_an_empty_cache_is_a_noop() {
let pool = BrainPool::new(test_config(8, Duration::from_secs(100)));
pool.sweep_idle();
assert_eq!(pool.cached_len(), 0);
}
#[tokio::test]
async fn probe_a_dead_address_is_false() {
let pool = BrainPool::new(test_config(8, Duration::from_secs(900)));
assert!(!pool.probe(Duration::from_millis(200)).await);
}
#[test]
fn shared_mode_records_its_service_credential_and_no_percredential_cache() {
let pool = BrainPool::shared(
test_config(8, Duration::from_secs(900)),
"service-key".to_string(),
);
assert_eq!(pool.shared_service_credential(), Some("service-key"));
assert_eq!(
pool.cached_len(),
0,
"shared mode keeps no per-credential cache"
);
assert_eq!(pool.shared_pool_initialized(), Some(false), "built lazily");
}
#[test]
fn new_is_per_credential_not_shared() {
let pool = BrainPool::new(test_config(8, Duration::from_secs(900)));
assert_eq!(pool.shared_service_credential(), None);
assert_eq!(pool.shared_pool_initialized(), None);
}
#[tokio::test]
async fn shared_mode_ignores_the_credential_argument() {
let pool = BrainPool::shared(
test_config(8, Duration::from_secs(900)),
"service-key".to_string(),
);
let a = pool.client_for("tenant-a").await;
let b = pool.client_for("tenant-b").await;
assert!(a.is_err(), "dead addr: connect fails");
assert!(b.is_err(), "dead addr: connect fails");
assert_eq!(pool.cached_len(), 0);
assert_eq!(pool.shared_service_credential(), Some("service-key"));
}
#[test]
fn sweep_idle_in_shared_mode_is_a_noop() {
let pool = BrainPool::shared(
test_config(8, Duration::from_secs(1)),
"service-key".to_string(),
);
pool.sweep_idle();
assert_eq!(pool.cached_len(), 0);
}
}