use std::collections::{HashMap, VecDeque};
use std::fmt;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Mutex, MutexGuard};
use bytes::Bytes;
use ruststream::testing::Coordinator;
use ruststream::{HeaderMap, RawMessage};
use tokio::sync::mpsc;
use crate::address::Routing;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct SubscriptionId(u64);
#[derive(Debug, Clone)]
pub(crate) struct Delivery {
pub(crate) payload: Bytes,
pub(crate) headers: HeaderMap,
pub(crate) count: Option<u32>,
}
type DeliverySender = mpsc::UnboundedSender<Delivery>;
pub(crate) type DeliveryReceiver = mpsc::UnboundedReceiver<Delivery>;
struct Subscription {
address: String,
routing: Routing,
sender: DeliverySender,
}
#[derive(Default)]
struct RouterState {
subscriptions: HashMap<SubscriptionId, Subscription>,
log: HashMap<String, Vec<RawMessage>>,
anycast_turn: HashMap<String, usize>,
queues: HashMap<String, VecDeque<Delivery>>,
outstanding: HashMap<SubscriptionId, Vec<Delivery>>,
closed: bool,
}
#[derive(Default)]
pub(crate) struct AddressRouter {
state: Mutex<RouterState>,
next_id: AtomicU64,
}
impl AddressRouter {
fn state(&self) -> MutexGuard<'_, RouterState> {
self.state
.lock()
.expect("amqp in-process router mutex poisoned")
}
pub(crate) fn subscribe(
&self,
address: String,
routing: Routing,
coordinator: Option<&Coordinator>,
) -> Option<(SubscriptionId, DeliveryReceiver)> {
let (sender, receiver) = mpsc::unbounded_channel();
let id = SubscriptionId(self.next_id.fetch_add(1, Ordering::Relaxed));
let mut state = self.state();
if state.closed {
return None;
}
if routing == Routing::Anycast {
let stored = state.queues.entry(address.clone()).or_default();
for delivery in stored.drain(..) {
send(&sender, delivery, coordinator);
}
}
state.subscriptions.insert(
id,
Subscription {
address,
routing,
sender,
},
);
drop(state);
Some((id, receiver))
}
pub(crate) fn termini(&self, address: &str) -> (usize, usize) {
let state = self.state();
let on = || {
state
.subscriptions
.values()
.filter(|sub| sub.address == address)
};
let anycast = on().filter(|sub| sub.routing == Routing::Anycast).count();
let multicast = on().filter(|sub| sub.routing == Routing::Multicast).count();
drop(state);
(anycast, multicast)
}
pub(crate) fn recipients(&self, address: &str) -> Vec<bool> {
let state = self.state();
let mut attached: Vec<(SubscriptionId, Routing)> = state
.subscriptions
.iter()
.filter(|(_, sub)| sub.address == address)
.map(|(id, sub)| (*id, sub.routing))
.collect();
attached.sort_unstable_by_key(|(id, _)| *id);
let competing = attached
.iter()
.filter(|(_, routing)| *routing == Routing::Anycast)
.count();
let turn = state.anycast_turn.get(address).copied().unwrap_or(0);
drop(state);
let mut anycast = 0;
attached
.into_iter()
.map(|(_, routing)| match routing {
Routing::Multicast => true,
Routing::Anycast => {
let picked = anycast == turn % competing;
anycast += 1;
picked
}
})
.collect()
}
pub(crate) fn unsubscribe(
&self,
id: SubscriptionId,
pending: Vec<Delivery>,
coordinator: Option<&Coordinator>,
) {
let mut state = self.state();
let Some(sub) = state.subscriptions.remove(&id) else {
return;
};
let held = state.outstanding.remove(&id).unwrap_or_default();
if sub.routing == Routing::Anycast {
for delivery in held.into_iter().chain(pending) {
hand_to_one(&mut state, &sub.address, &delivery, coordinator);
}
}
let consumed = !state
.subscriptions
.values()
.any(|other| other.address == sub.address && other.routing == Routing::Anycast);
if consumed
&& state
.queues
.get(&sub.address)
.is_some_and(VecDeque::is_empty)
{
state.queues.remove(&sub.address);
}
drop(state);
}
pub(crate) fn release(
&self,
id: SubscriptionId,
address: &str,
delivery: Delivery,
coordinator: Option<&Coordinator>,
) {
let mut state = self.state();
if state.closed {
return;
}
if state.subscriptions.contains_key(&id) {
state.outstanding.entry(id).or_default().push(delivery);
} else {
hand_to_one(&mut state, address, &delivery, coordinator);
}
drop(state);
}
pub(crate) fn publish(
&self,
address: &str,
delivery: &Delivery,
coordinator: Option<&Coordinator>,
) -> bool {
let mut state = self.state();
if state.closed {
return false;
}
publish_one(&mut state, address, delivery, coordinator);
drop(state);
true
}
pub(crate) fn publish_all<'a>(
&self,
messages: impl IntoIterator<Item = (&'a str, &'a Delivery)>,
coordinator: Option<&Coordinator>,
) -> bool {
let mut state = self.state();
if state.closed {
return false;
}
for (address, delivery) in messages {
publish_one(&mut state, address, delivery, coordinator);
}
drop(state);
true
}
pub(crate) fn requeue(
&self,
id: SubscriptionId,
address: &str,
delivery: Delivery,
coordinator: Option<&Coordinator>,
) -> bool {
let mut state = self.state();
if state.closed {
return false;
}
if let Some(sub) = state.subscriptions.get(&id) {
send(&sub.sender, delivery, coordinator);
} else {
hand_to_one(&mut state, address, &delivery, coordinator);
}
drop(state);
true
}
pub(crate) fn published(&self, address: &str) -> Vec<RawMessage> {
self.state().log.get(address).cloned().unwrap_or_default()
}
pub(crate) fn close(&self) {
let mut state = self.state();
state.closed = true;
state.subscriptions.clear();
state.anycast_turn.clear();
state.queues.clear();
state.outstanding.clear();
}
}
fn publish_one(
state: &mut RouterState,
address: &str,
delivery: &Delivery,
coordinator: Option<&Coordinator>,
) {
let snapshot = RawMessage::new(address.to_owned(), delivery.payload.clone())
.with_headers(delivery.headers.clone());
state
.log
.entry(address.to_owned())
.or_default()
.push(snapshot);
for sub in state.subscriptions.values() {
if sub.address == address && sub.routing == Routing::Multicast {
send(&sub.sender, delivery.clone(), coordinator);
}
}
hand_to_one(state, address, delivery, coordinator);
}
fn hand_to_one(
state: &mut RouterState,
address: &str,
delivery: &Delivery,
coordinator: Option<&Coordinator>,
) {
let mut competing: Vec<(SubscriptionId, DeliverySender)> = state
.subscriptions
.iter()
.filter(|(_, sub)| sub.address == address && sub.routing == Routing::Anycast)
.map(|(id, sub)| (*id, sub.sender.clone()))
.collect();
if competing.is_empty() {
if let Some(queue) = state.queues.get_mut(address) {
queue.push_back(delivery.clone());
}
return;
}
competing.sort_unstable_by_key(|(id, _)| *id);
let turn = state.anycast_turn.entry(address.to_owned()).or_insert(0);
let first = *turn;
*turn = turn.wrapping_add(1);
for offset in 0..competing.len() {
let (_, sender) = &competing[first.wrapping_add(offset) % competing.len()];
if send(sender, delivery.clone(), coordinator) {
return;
}
}
if let Some(queue) = state.queues.get_mut(address) {
queue.push_back(delivery.clone());
}
}
fn send(sender: &DeliverySender, delivery: Delivery, coordinator: Option<&Coordinator>) -> bool {
if let Some(coordinator) = coordinator {
coordinator.enqueued();
}
let sent = sender.send(delivery).is_ok();
if !sent && let Some(coordinator) = coordinator {
coordinator.consumed();
}
sent
}
impl fmt::Debug for AddressRouter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let state = self.state();
f.debug_struct("AddressRouter")
.field("subscriptions", &state.subscriptions.len())
.field("logged_addresses", &state.log.len())
.finish_non_exhaustive()
}
}