use crate::mixnet::{MixnetClient, MixnetClientBuilder, NymNetworkDetails};
use anyhow::Result;
use nym_crypto::asymmetric::ed25519;
use std::fmt;
use std::sync::Arc;
use tokio::sync::RwLock;
use tokio_util::sync::CancellationToken;
use tracing::{debug, info, warn};
pub struct ClientPool {
clients: Arc<RwLock<Vec<Arc<MixnetClient>>>>,
client_pool_reserve_number: usize,
cancel_token: CancellationToken,
}
impl fmt::Debug for ClientPool {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let clients_debug = match self.clients.try_read() {
Ok(clients) => {
if f.alternate() {
clients
.iter()
.enumerate()
.map(|(i, client)| format!("\n {}: {}", i, client.nym_address()))
.collect::<Vec<_>>()
.join(",")
} else {
clients
.iter()
.map(|client| client.nym_address().to_string())
.collect::<Vec<_>>()
.join(", ")
}
}
Err(_) => "<locked>".to_string(),
};
let mut debug_struct = f.debug_struct("Pool");
debug_struct
.field(
"client_pool_reserve_number",
&self.client_pool_reserve_number,
)
.field("clients", &format_args!("[{clients_debug}]"));
debug_struct.finish()
}
}
impl Clone for ClientPool {
fn clone(&self) -> Self {
Self {
clients: Arc::clone(&self.clients),
client_pool_reserve_number: self.client_pool_reserve_number,
cancel_token: self.cancel_token.clone(),
}
}
}
impl ClientPool {
pub fn new(client_pool_reserve_number: usize) -> Self {
ClientPool {
clients: Arc::new(RwLock::new(Vec::new())),
client_pool_reserve_number,
cancel_token: CancellationToken::new(),
}
}
pub async fn start(&self) -> Result<()> {
loop {
let spawned_clients = self.clients.read().await.len();
let addresses = self;
debug!(
"Currently spawned clients: {}: {:?}",
spawned_clients, addresses
);
if self.cancel_token.is_cancelled() {
break Ok(());
}
if spawned_clients >= self.client_pool_reserve_number {
debug!("Got enough clients already: sleeping");
} else {
info!(
"Clients in reserve = {}, reserve amount = {}, spawning new client",
spawned_clients, self.client_pool_reserve_number
);
let client = loop {
let net = NymNetworkDetails::new_from_env();
match MixnetClientBuilder::new_ephemeral()
.network_details(net)
.build()?
.connect_to_mixnet()
.await
{
Ok(client) => break client,
Err(err) => {
warn!("Error creating client: {:?}, will retry in 100ms", err);
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
}
}
};
self.clients.write().await.push(Arc::new(client));
}
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
}
}
pub async fn start_with_specified_gateway(&self, gateway: ed25519::PublicKey) -> Result<()> {
loop {
let spawned_clients = self.clients.read().await.len();
let addresses = self;
debug!(
"Currently spawned clients: {}: {:?}",
spawned_clients, addresses
);
if self.cancel_token.is_cancelled() {
break Ok(());
}
if spawned_clients >= self.client_pool_reserve_number {
debug!("Got enough clients already: sleeping");
} else {
info!(
"Clients in reserve = {}, reserve amount = {}, spawning new client",
spawned_clients, self.client_pool_reserve_number
);
let client = loop {
let net = NymNetworkDetails::new_from_env();
match MixnetClientBuilder::new_ephemeral()
.network_details(net)
.request_gateway(gateway.to_string())
.build()?
.connect_to_mixnet()
.await
{
Ok(client) => break client,
Err(err) => {
warn!("Error creating client: {:?}, will retry in 100ms", err);
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
}
}
};
self.clients.write().await.push(Arc::new(client));
}
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
}
}
pub async fn disconnect_pool(&self) {
info!("Triggering Client Pool disconnect");
self.cancel_token.cancel();
info!(
"Client pool cancellation token cancelled: {}",
self.cancel_token.is_cancelled()
);
let mut clients = self.clients.write().await;
while let Some(arc_client) = clients.pop() {
if let Ok(client) = Arc::try_unwrap(arc_client) {
info!("Killing reserve client {}", client.nym_address());
client.disconnect().await;
}
}
}
pub async fn get_mixnet_client(&self) -> Option<MixnetClient> {
debug!("Grabbing client from pool");
let mut clients = self.clients.write().await;
clients
.pop()
.and_then(|arc_client| Arc::try_unwrap(arc_client).ok())
}
pub async fn get_client_count(&self) -> usize {
self.clients.read().await.len()
}
pub async fn get_pool_reserve(&self) -> usize {
self.client_pool_reserve_number
}
}