use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::fmt::{Debug, Display};
use std::future::Future;
use std::hash::Hash;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, OnceLock, PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard};
use log::{debug, trace};
use tokio::sync::{broadcast, Mutex};
use crate::accounts::types::AccountId;
use crate::messages::{shared_channel_configuration, IncomingMessages, OutgoingMessages, ResponseMessage};
use crate::transport::common::LeaseRef;
use crate::transport::{Admit, BoundState, BufferBound, RoutedItem, SharedCounts, SharedTicket};
use crate::Error;
pub(super) type BroadcastSender = broadcast::Sender<RoutedItem>;
#[derive(Debug)]
pub(super) struct Route {
pub(super) sender: BroadcastSender,
pub(super) lease: LeaseRef,
bound: Option<RouteBound>,
}
#[derive(Debug)]
struct RouteBound {
state: BoundState,
sent: AtomicUsize,
reads: Arc<AtomicUsize>,
}
impl RouteBound {
fn unread(&self) -> usize {
self.sent.load(Ordering::Relaxed).saturating_sub(self.reads.load(Ordering::Relaxed))
}
}
impl Route {
pub(super) fn unbounded(sender: BroadcastSender, lease: LeaseRef) -> Self {
Self { sender, lease, bound: None }
}
pub(super) fn bounded(sender: BroadcastSender, lease: LeaseRef, bound: BufferBound, reads: Arc<AtomicUsize>) -> Self {
let bound = RouteBound {
state: BoundState::new(bound),
sent: AtomicUsize::new(0),
reads,
};
Self {
sender,
lease,
bound: Some(bound),
}
}
fn closed(&self) -> bool {
self.bound.as_ref().is_some_and(|bound| bound.state.closed())
}
pub(super) fn released(&self, lease: &LeaseRef) -> bool {
self.lease.is(lease) && !self.lease.is_live()
}
pub(super) fn deliver(&self, id: impl Display, item: RoutedItem) {
let item = match &self.bound {
Some(bound) => match bound.state.admit(&item, bound.unread()) {
Admit::Deliver => {
bound.sent.fetch_add(1, Ordering::Relaxed);
item
}
Admit::Overflow => bound.state.overflow_error(),
Admit::Discard => {
trace!("discarding item for closed route {id}");
return;
}
},
None => item,
};
let _ = self.sender.send(item);
}
}
#[derive(Debug)]
pub(super) struct SenderHash<K> {
routes: RwLock<HashMap<K, Route>>,
}
impl<K: Hash + Eq + Display + Debug> SenderHash<K> {
pub(super) fn new() -> Self {
Self {
routes: RwLock::new(HashMap::new()),
}
}
fn read(&self) -> RwLockReadGuard<'_, HashMap<K, Route>> {
self.routes.read().unwrap_or_else(PoisonError::into_inner)
}
fn write(&self) -> RwLockWriteGuard<'_, HashMap<K, Route>> {
self.routes.write().unwrap_or_else(PoisonError::into_inner)
}
pub(super) fn insert(&self, id: K, route: Route) {
self.write().insert(id, route);
}
pub(super) fn with_route<R>(&self, id: &K, f: impl FnOnce(&Route) -> R) -> Option<R> {
self.read().get(id).map(f)
}
pub(super) fn deliver(&self, id: &K, item: RoutedItem) -> Result<(), RoutedItem> {
match self.read().get(id) {
Some(route) => {
route.deliver(id, item);
Ok(())
}
None => Err(item),
}
}
fn remove_if(&self, id: K, pred: impl FnOnce(&Route) -> bool) -> bool {
match self.write().entry(id) {
Entry::Occupied(route) if pred(route.get()) => {
route.remove();
true
}
_ => false,
}
}
pub(super) fn remove_if_same(&self, id: K, lease: &LeaseRef) {
self.remove_if(id, |route| route.lease.is(lease));
}
pub(super) fn release(&self, id: K, lease: &LeaseRef, kind: &str) {
let label = id.to_string();
let removed = self.remove_if(id, |route| route.released(lease));
debug!("cleanup {kind} channel {label}: removed={removed}");
}
pub(super) fn prune_dead(&self) {
if self.read().is_empty() {
return;
}
let mut routes = self.write();
let before = routes.len();
routes.retain(|_, route| route.lease.is_live());
debug!("pruned {} dead routes", before - routes.len());
}
pub(super) fn fail_all(&self, item: impl Fn() -> RoutedItem) {
let mut routes = self.write();
for route in routes.values().filter(|route| !route.closed()) {
let _ = route.sender.send(item());
}
routes.clear();
}
pub(super) fn clear(&self) {
self.write().clear();
}
#[cfg(test)]
pub(super) fn sender(&self, id: &K) -> Option<BroadcastSender> {
self.with_route(id, |route| route.sender.clone())
}
#[cfg(test)]
pub(super) fn contains(&self, id: &K) -> bool {
self.read().contains_key(id)
}
#[cfg(test)]
pub(super) fn len(&self) -> usize {
self.read().len()
}
#[cfg(test)]
pub(super) fn is_empty(&self) -> bool {
self.read().is_empty()
}
}
#[derive(Debug)]
pub(super) struct SharedChannels {
channels: RwLock<HashMap<OutgoingMessages, SharedChannel>>,
counts: Mutex<SharedCounts>,
}
#[derive(Debug)]
struct SharedChannel {
responses: &'static [IncomingMessages],
capacity: usize,
sender: OnceLock<BroadcastSender>,
}
impl SharedChannel {
fn subscribe(&self) -> broadcast::Receiver<RoutedItem> {
self.sender.get_or_init(|| broadcast::channel(self.capacity).0).subscribe()
}
}
impl SharedChannels {
pub(super) fn new(capacity: impl Fn(OutgoingMessages) -> usize) -> Self {
let channels = shared_channel_configuration::CHANNEL_MAPPINGS
.iter()
.map(|mapping| {
let channel = SharedChannel {
responses: mapping.responses,
capacity: capacity(mapping.request),
sender: OnceLock::new(),
};
(mapping.request, channel)
})
.collect();
Self {
channels: RwLock::new(channels),
counts: Mutex::new(SharedCounts::default()),
}
}
pub(super) async fn subscribe<F: Future<Output = Result<(), Error>>>(
&self,
message_type: OutgoingMessages,
account: Option<&AccountId>,
write: impl FnOnce() -> F,
) -> Result<(broadcast::Receiver<RoutedItem>, SharedTicket), Error> {
let receiver = self
.channels()
.get(&message_type)
.map(SharedChannel::subscribe)
.ok_or_else(|| Error::InvalidArgument(format!("No shared channel configured for message type: {message_type:?}")))?;
let mut counts = self.counts.lock().await;
counts.check_account_updates(account)?;
write().await?;
Ok((receiver, counts.subscribe(message_type, account)))
}
pub(super) async fn unsubscribe<F: Future<Output = Result<(), Error>>>(
&self,
ticket: SharedTicket,
write: impl FnOnce() -> F,
) -> Result<(), Error> {
let mut counts = self.counts.lock().await;
if counts.unsubscribe(ticket) {
write().await
} else {
Ok(())
}
}
fn channels(&self) -> RwLockReadGuard<'_, HashMap<OutgoingMessages, SharedChannel>> {
self.channels.read().unwrap_or_else(PoisonError::into_inner)
}
pub(super) async fn reset_counts(&self) {
self.counts.lock().await.reset();
}
fn send_to(&self, filter: impl Fn(&SharedChannel) -> bool, item: impl Fn() -> RoutedItem) -> usize {
let channels = self.channels();
let mut selected = 0;
for (request, channel) in channels.iter().filter(|(_, channel)| filter(channel)) {
selected += 1;
if channel.sender.get().is_none_or(|sender| sender.send(item()).is_err()) {
trace!("no shared subscription for {request:?}");
}
}
selected
}
pub(super) fn send_message(&self, message_type: IncomingMessages, message: &ResponseMessage) -> bool {
self.send_to(|channel| channel.responses.contains(&message_type), || message.clone().into()) > 0
}
pub(super) fn notify_all(&self, item: impl Fn() -> RoutedItem) {
self.send_to(|_| true, item);
}
pub(super) fn fail_one_shot_channels(&self, item: impl Fn() -> RoutedItem) {
let one_shot = shared_channel_configuration::exclusive_one_shot_response_types();
self.send_to(|channel| channel.responses.iter().any(|r| one_shot.contains(r)), item);
}
pub(super) fn close(&self, item: impl Fn() -> RoutedItem) {
let mut channels = self.channels.write().unwrap_or_else(PoisonError::into_inner);
for sender in channels.values().filter_map(|channel| channel.sender.get()) {
let _ = sender.send(item());
}
channels.clear();
}
#[cfg(test)]
pub(super) async fn live(&self, message_type: OutgoingMessages) -> usize {
self.counts.lock().await.live(message_type)
}
#[cfg(test)]
pub(super) async fn account_updates(&self) -> Option<AccountId> {
self.counts.lock().await.account_updates().cloned()
}
}
#[cfg(test)]
#[path = "registry_tests.rs"]
mod tests;