use std::collections::{BTreeMap, HashMap};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use tokio::sync::Semaphore;
use crate::lifecycle::{Lifecycle, PeerPath, aggregate};
use crate::status::PeerView;
pub(crate) const MAX_CONCURRENT_STREAMS_PER_PEER: usize = 64;
struct Budget {
slots: Arc<Semaphore>,
connections: usize,
}
pub(crate) struct PeerRegistry {
peers: Mutex<BTreeMap<u64, (Arc<str>, PeerPath)>>,
budgets: Mutex<HashMap<Arc<str>, Budget>>,
next: AtomicU64,
}
impl PeerRegistry {
pub(crate) fn new() -> Self {
Self {
peers: Mutex::new(BTreeMap::new()),
budgets: Mutex::new(HashMap::new()),
next: AtomicU64::new(0),
}
}
pub(crate) fn slots(&self, name: &Arc<str>) -> Arc<Semaphore> {
let mut budgets = self.lock_budgets();
let budget = budgets.entry(name.clone()).or_insert_with(|| Budget {
slots: Arc::new(Semaphore::new(MAX_CONCURRENT_STREAMS_PER_PEER)),
connections: 0,
});
budget.connections += 1;
let slots = budget.slots.clone();
drop(budgets);
slots
}
pub(crate) fn add(&self, name: Arc<str>, path: PeerPath, lifecycle: &Lifecycle) -> u64 {
let id = self.next.fetch_add(1, Ordering::Relaxed);
self.mutate(lifecycle, |peers| {
peers.insert(id, (name, path));
});
id
}
pub(crate) fn remove(&self, id: u64, lifecycle: &Lifecycle) {
let mut departed = None;
self.mutate(lifecycle, |peers| {
departed = peers.remove(&id).map(|(name, _)| name);
});
if let Some(name) = departed {
self.release(&name);
}
}
fn release(&self, name: &Arc<str>) {
let mut budgets = self.lock_budgets();
if let Some(budget) = budgets.get_mut(name) {
budget.connections = budget.connections.saturating_sub(1);
if budget.connections == 0 {
budgets.remove(name);
}
}
}
pub(crate) fn views(&self) -> Vec<PeerView> {
self.lock()
.values()
.map(|(name, path)| PeerView {
fingerprint: name.to_string(),
path: aggregate(&[*path]),
})
.collect()
}
fn mutate(
&self,
lifecycle: &Lifecycle,
f: impl FnOnce(&mut BTreeMap<u64, (Arc<str>, PeerPath)>),
) {
let mut guard = self.lock();
f(&mut guard);
let paths: Vec<PeerPath> = guard.values().map(|(_, path)| *path).collect();
drop(guard);
lifecycle.set_status(aggregate(&paths));
}
fn lock(&self) -> std::sync::MutexGuard<'_, BTreeMap<u64, (Arc<str>, PeerPath)>> {
self.peers
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn lock_budgets(&self) -> std::sync::MutexGuard<'_, HashMap<Arc<str>, Budget>> {
self.budgets
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
}
#[cfg(test)]
#[path = "peers_tests.rs"]
mod peers_tests;