use std::net::SocketAddr;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, RwLock};
use crate::client::{Auth, BrainClient, ClientConfig};
use crate::error::{BrainError, Result};
struct Slot {
client: RwLock<Arc<BrainClient>>,
reconnecting: tokio::sync::Mutex<()>,
}
impl Slot {
fn new(client: BrainClient) -> Self {
Self {
client: RwLock::new(Arc::new(client)),
reconnecting: tokio::sync::Mutex::new(()),
}
}
fn current(&self) -> Arc<BrainClient> {
Arc::clone(&self.client.read().expect("pool slot lock poisoned"))
}
}
pub struct Pool {
slots: Vec<Slot>,
next: AtomicUsize,
addr: SocketAddr,
config: ClientConfig,
}
impl std::fmt::Debug for Pool {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Pool")
.field("size", &self.slots.len())
.finish_non_exhaustive()
}
}
impl Pool {
pub async fn connect(addr: SocketAddr, size: usize, auth: Auth) -> Result<Self> {
Self::connect_with(addr, size, &ClientConfig::new(auth)).await
}
pub async fn connect_with(
addr: SocketAddr,
size: usize,
config: &ClientConfig,
) -> Result<Self> {
if size == 0 {
return Err(BrainError::Protocol(
"connection pool size must be >= 1".to_string(),
));
}
let mut slots = Vec::with_capacity(size);
for _ in 0..size {
let cfg = config.clone();
match BrainClient::connect_with(addr, cfg).await {
Ok(client) => slots.push(Slot::new(client)),
Err(e) => {
drop(slots);
return Err(e);
}
}
}
Ok(Self {
slots,
next: AtomicUsize::new(0),
addr,
config: config.clone(),
})
}
#[must_use]
pub fn size(&self) -> usize {
self.slots.len()
}
fn next_slot(&self) -> &Slot {
let idx = self.next.fetch_add(1, Ordering::Relaxed) % self.slots.len();
&self.slots[idx]
}
#[must_use]
pub fn get(&self) -> Arc<BrainClient> {
self.next_slot().current()
}
pub async fn get_healthy(&self) -> Result<Arc<BrainClient>> {
let slot = self.next_slot();
let client = slot.current();
if !client.is_closed() {
return Ok(client);
}
let _guard = slot.reconnecting.lock().await;
let client = slot.current();
if !client.is_closed() {
return Ok(client);
}
let fresh = Arc::new(BrainClient::connect_with(self.addr, self.config.clone()).await?);
*slot.client.write().expect("pool slot lock poisoned") = Arc::clone(&fresh);
Ok(fresh)
}
}