use std::collections::HashMap;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::sync::{Arc, Mutex, OnceLock};
use std::time::{Duration, Instant};
use super::client::{self, ClientService};
use super::config::ResolvedTransport;
const IDLE_TTL: Duration = Duration::from_mins(5);
struct Entry {
service: Arc<ClientService>,
last_used: Instant,
}
fn pool() -> &'static Mutex<HashMap<u64, Entry>> {
static POOL: OnceLock<Mutex<HashMap<u64, Entry>>> = OnceLock::new();
POOL.get_or_init(|| Mutex::new(HashMap::new()))
}
#[must_use]
pub fn key(transport: &ResolvedTransport) -> u64 {
let mut h = DefaultHasher::new();
format!("{transport:?}").hash(&mut h);
h.finish()
}
pub async fn acquire(
transport: &ResolvedTransport,
timeout: Duration,
) -> Result<Arc<ClientService>, String> {
let k = key(transport);
{
let mut map = lock();
let now = Instant::now();
map.retain(|_, e| now.duration_since(e.last_used) < IDLE_TTL && !e.service.is_closed());
if let Some(entry) = map.get_mut(&k) {
entry.last_used = now;
return Ok(entry.service.clone());
}
}
let service = Arc::new(client::open(transport, timeout).await?);
let mut map = lock();
map.insert(
k,
Entry {
service: service.clone(),
last_used: Instant::now(),
},
);
Ok(service)
}
pub fn evict(key: u64) {
lock().remove(&key);
}
pub fn clear() {
lock().clear();
}
#[must_use]
pub fn len() -> usize {
lock().len()
}
fn lock() -> std::sync::MutexGuard<'static, HashMap<u64, Entry>> {
pool()
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::BTreeMap;
fn stdio(cmd: &str) -> ResolvedTransport {
ResolvedTransport::Stdio {
command: cmd.into(),
args: vec![],
env: BTreeMap::new(),
binary_sha256: String::new(),
capabilities: None,
}
}
#[test]
fn key_is_stable_and_wiring_sensitive() {
assert_eq!(key(&stdio("a")), key(&stdio("a")), "same wiring → same key");
assert_ne!(
key(&stdio("a")),
key(&stdio("b")),
"different command → different key"
);
}
#[test]
fn evict_and_clear_are_safe_when_empty() {
clear();
evict(key(&stdio("never-pooled")));
clear();
assert_eq!(len(), 0);
}
}