use std::collections::{BTreeMap, HashMap, VecDeque};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex, MutexGuard, OnceLock};
use bytes::Bytes;
use lapin::types::{FieldTable, ShortString};
use lapin::{BasicProperties, ExchangeKind};
use ruststream::testing::Coordinator;
use ruststream::{HeaderMap, RawMessage};
use tokio::runtime::Handle;
use tokio::sync::mpsc;
use super::deliveries::QueueBehaviour;
use super::matching::{headers_match, topic_matches};
use crate::convert;
use crate::error::AmqpError;
use crate::publish_step::{EXPIRATION_HEADER, LapinPublishOptions, PRIORITY_HEADER};
use crate::queue::{QueueKind, QueueSpec};
const MAX_MESSAGE_SIZE: usize = 16 * 1024 * 1024;
const MAX_PRIORITY: &str = "x-max-priority";
#[derive(Debug, Clone)]
pub(crate) struct BusDelivery {
pub(crate) payload: Bytes,
pub(crate) headers: HeaderMap,
pub(crate) exchange: String,
pub(crate) routing_key: String,
pub(crate) redelivered: bool,
pub(crate) returns: Option<u64>,
}
pub(crate) type DeliverySender = mpsc::UnboundedSender<BusDelivery>;
pub(crate) type DeliveryReceiver = mpsc::UnboundedReceiver<BusDelivery>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct ConsumerId(u64);
#[derive(Debug)]
struct Consumer {
queue: String,
sender: DeliverySender,
}
#[derive(Debug, Clone)]
struct Bound {
exchange: String,
kind: ExchangeKind,
routing_key: String,
arguments: FieldTable,
queue: String,
}
#[derive(Debug, Clone, PartialEq)]
struct DeclaredQueue {
kind: QueueKind,
durable: bool,
exclusive: bool,
auto_delete: bool,
arguments: FieldTable,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct DeclaredExchange {
kind: ExchangeKind,
durable: bool,
auto_delete: bool,
}
#[derive(Debug, Default)]
struct Routes {
consumers: BTreeMap<ConsumerId, Consumer>,
bindings: Vec<Bound>,
queues: HashMap<String, DeclaredQueue>,
exchanges: HashMap<String, DeclaredExchange>,
log: HashMap<String, Vec<RawMessage>>,
dispatched: HashMap<String, usize>,
stored: HashMap<String, Stored>,
}
#[derive(Debug, Default)]
struct Stored {
auto_delete: bool,
waiting: VecDeque<BusDelivery>,
}
impl Routes {
fn next_consumer(&mut self, queue: &str) -> Option<DeliverySender> {
let consumers: Vec<&Consumer> = self
.consumers
.values()
.filter(|consumer| consumer.queue == queue)
.collect();
if consumers.is_empty() {
return None;
}
let turn = self.dispatched.entry(queue.to_owned()).or_default();
let index = *turn % consumers.len();
*turn = turn.wrapping_add(1);
Some(consumers[index].sender.clone())
}
fn hand_over(
&mut self,
queue: &str,
delivery: BusDelivery,
) -> Option<(DeliverySender, BusDelivery)> {
if let Some(sender) = self.next_consumer(queue) {
return Some((sender, delivery));
}
if let Some(stored) = self.stored.get_mut(queue) {
stored.waiting.push_back(delivery);
}
None
}
fn next_turn(&self, queue: &str) -> Option<usize> {
let consumers = self
.consumers
.values()
.filter(|consumer| consumer.queue == queue)
.count();
if consumers == 0 {
return None;
}
Some(self.dispatched.get(queue).copied().unwrap_or_default() % consumers)
}
fn targets(
&self,
exchange: &str,
routing_key: &str,
table: Option<&FieldTable>,
) -> Vec<String> {
if exchange.is_empty() {
return vec![routing_key.to_owned()];
}
let mut queues: Vec<String> = Vec::new();
for bound in self
.bindings
.iter()
.filter(|bound| bound.exchange == exchange)
{
let routed = match &bound.kind {
ExchangeKind::Direct => bound.routing_key == routing_key,
ExchangeKind::Topic => topic_matches(&bound.routing_key, routing_key),
ExchangeKind::Fanout => true,
ExchangeKind::Headers => headers_match(&bound.arguments, table),
ExchangeKind::Custom(_) => false,
};
if routed && !queues.contains(&bound.queue) {
queues.push(bound.queue.clone());
}
}
queues
}
}
pub(crate) struct Bus {
routes: Mutex<Routes>,
next_consumer: AtomicU64,
closed: AtomicBool,
coordinator: OnceLock<Coordinator>,
inbox_seq: AtomicU64,
runtime: Handle,
}
impl Bus {
pub(crate) fn new(runtime: Handle) -> Arc<Self> {
Arc::new(Self {
routes: Mutex::new(Routes::default()),
next_consumer: AtomicU64::new(0),
closed: AtomicBool::new(false),
coordinator: OnceLock::new(),
inbox_seq: AtomicU64::new(0),
runtime,
})
}
pub(crate) const fn runtime(&self) -> &Handle {
&self.runtime
}
fn routes(&self) -> MutexGuard<'_, Routes> {
self.routes
.lock()
.expect("in-process routes mutex poisoned")
}
pub(crate) fn install(&self, coordinator: Coordinator) {
let _ = self.coordinator.set(coordinator);
}
pub(crate) fn coordinator(&self) -> Option<Coordinator> {
self.coordinator.get().cloned()
}
pub(crate) fn next_inbox(&self) -> u64 {
self.inbox_seq.fetch_add(1, Ordering::Relaxed)
}
pub(crate) fn ensure_live(&self, target: &str) -> Result<(), AmqpError> {
if self.is_closed() {
return Err(AmqpError::closed(target));
}
Ok(())
}
pub(crate) fn is_closed(&self) -> bool {
self.closed.load(Ordering::Acquire)
}
pub(crate) fn close(&self) {
self.closed.store(true, Ordering::Release);
self.routes().consumers.clear();
}
pub(crate) fn consume_queue(
&self,
queue: &str,
auto_delete: bool,
) -> (ConsumerId, DeliveryReceiver) {
let mut routes = self.routes();
let opened = self.register(&mut routes, queue.to_owned());
let waiting = std::mem::take(
&mut routes
.stored
.entry(queue.to_owned())
.or_insert(Stored {
auto_delete,
waiting: VecDeque::new(),
})
.waiting,
);
for delivery in waiting {
self.hand_over(&mut routes, queue, delivery);
}
drop(routes);
opened
}
pub(crate) fn consume(&self, queue: String) -> (ConsumerId, DeliveryReceiver) {
self.register(&mut self.routes(), queue)
}
fn register(&self, routes: &mut Routes, queue: String) -> (ConsumerId, DeliveryReceiver) {
let id = ConsumerId(self.next_consumer.fetch_add(1, Ordering::Relaxed));
let (sender, receiver) = mpsc::unbounded_channel();
routes.consumers.insert(id, Consumer { queue, sender });
(id, receiver)
}
pub(crate) fn cancel(&self, id: ConsumerId) {
let mut routes = self.routes();
let Some(consumer) = routes.consumers.remove(&id) else {
return;
};
let last = !routes
.consumers
.values()
.any(|other| other.queue == consumer.queue);
if last
&& routes
.stored
.get(&consumer.queue)
.is_some_and(|stored| stored.auto_delete)
{
routes.stored.remove(&consumer.queue);
}
}
pub(crate) fn describe(
&self,
spec: &QueueSpec,
arguments: &FieldTable,
declares: bool,
) -> Result<(), AmqpError> {
let mut routes = self.routes();
if declares {
declare(&mut routes, spec, arguments)?;
}
for binding in &spec.bindings {
let exchange = binding.exchange.name();
let known = routes.bindings.iter().any(|bound| {
bound.exchange == exchange
&& bound.routing_key == binding.routing_key
&& bound.queue == spec.name
&& bound.arguments == binding.arguments
});
if !known {
routes.bindings.push(Bound {
exchange: exchange.to_owned(),
kind: binding.exchange.kind().clone(),
routing_key: binding.routing_key.clone(),
arguments: binding.arguments.clone(),
queue: spec.name.clone(),
});
}
}
drop(routes);
Ok(())
}
pub(crate) fn publish(
&self,
exchange: &str,
routing_key: &str,
payload: &[u8],
headers: &HeaderMap,
options: &LapinPublishOptions,
) -> Result<(), AmqpError> {
let properties = self.check(exchange, routing_key, headers, options)?;
Self::check_size(routing_key, payload.len())?;
let payload = Bytes::copy_from_slice(payload);
let queues =
{
let mut routes = self.routes();
routes.log.entry(routing_key.to_owned()).or_default().push(
RawMessage::new(routing_key, payload.clone()).with_headers(headers.clone()),
);
routes.targets(exchange, routing_key, properties.headers().as_ref())
};
let delivery = BusDelivery {
payload,
headers: convert::headers_from_properties(&properties),
exchange: exchange.to_owned(),
routing_key: routing_key.to_owned(),
redelivered: false,
returns: None,
};
for queue in queues {
self.deliver(&queue, delivery.clone());
}
Ok(())
}
pub(crate) fn check_size(routing_key: &str, len: usize) -> Result<(), AmqpError> {
if len > MAX_MESSAGE_SIZE {
return Err(AmqpError::Publish(
format!(
"a message body of {len} bytes to {routing_key:?} is over the server's \
max_message_size of {MAX_MESSAGE_SIZE} bytes"
)
.into(),
));
}
Ok(())
}
pub(crate) fn check(
&self,
exchange: &str,
routing_key: &str,
headers: &HeaderMap,
options: &LapinPublishOptions,
) -> Result<BasicProperties, AmqpError> {
self.ensure_live(routing_key)?;
convert::short(exchange, "exchange name")?;
convert::short(routing_key, "routing key")?;
convert::properties_for_publish(headers, options)
}
pub(crate) fn reroute(&self, exchange: &str, routing_key: &str, delivery: &BusDelivery) {
let mut own = delivery.headers.clone();
own.remove(PRIORITY_HEADER);
own.remove(EXPIRATION_HEADER);
let properties =
convert::properties_for_publish(&own, &LapinPublishOptions::default()).ok();
let table = properties
.as_ref()
.and_then(|properties| properties.headers().as_ref());
let queues = self.routes().targets(exchange, routing_key, table);
for queue in queues {
self.deliver(
&queue,
BusDelivery {
exchange: exchange.to_owned(),
routing_key: routing_key.to_owned(),
redelivered: false,
returns: None,
..delivery.clone()
},
);
}
}
pub(crate) fn deliver(&self, queue: &str, delivery: BusDelivery) {
self.hand_over(&mut self.routes(), queue, delivery);
}
fn hand_over(&self, routes: &mut Routes, queue: &str, delivery: BusDelivery) {
let Some((sender, delivery)) = routes.hand_over(queue, delivery) else {
return;
};
let coordinator = self.coordinator.get();
if let Some(coordinator) = coordinator {
coordinator.enqueued();
}
if sender.send(delivery).is_err()
&& let Some(coordinator) = coordinator
{
coordinator.consumed();
}
}
pub(crate) fn requeue_unread(
&self,
queue: &str,
delivery: BusDelivery,
behaviour: &QueueBehaviour,
) {
self.requeue(queue, delivery, behaviour);
if let Some(coordinator) = self.coordinator.get() {
coordinator.consumed();
}
}
pub(crate) fn requeue_unsettled(
&self,
queue: &str,
delivery: BusDelivery,
behaviour: &QueueBehaviour,
) {
self.requeue(queue, delivery, behaviour);
}
fn requeue(&self, queue: &str, mut delivery: BusDelivery, behaviour: &QueueBehaviour) {
if behaviour.spend(&mut delivery) {
behaviour.dead_letter(self, &delivery);
} else {
self.deliver(
queue,
BusDelivery {
redelivered: true,
..delivery
},
);
}
}
pub(crate) fn next_consumer_of(&self, queue: &str) -> Option<usize> {
self.routes().next_turn(queue)
}
pub(crate) fn published(&self, routing_key: &str) -> Vec<RawMessage> {
self.routes()
.log
.get(routing_key)
.cloned()
.unwrap_or_default()
}
}
impl std::fmt::Debug for Bus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Bus")
.field("closed", &self.is_closed())
.finish_non_exhaustive()
}
}
fn declare(routes: &mut Routes, spec: &QueueSpec, arguments: &FieldTable) -> Result<(), AmqpError> {
for binding in &spec.bindings {
let exchange = &binding.exchange;
if exchange.name().is_empty() {
return Err(refused(format!(
"queue {:?} binds to the default exchange, and the server permits no operation on \
it",
spec.name
)));
}
convert::short(exchange.name(), "exchange name")?;
convert::short(&binding.routing_key, "routing key")?;
if exchange.name().starts_with("amq.") {
continue;
}
let declared = DeclaredExchange {
kind: exchange.kind().clone(),
durable: exchange.is_durable(),
auto_delete: exchange.is_auto_delete(),
};
match routes.exchanges.get(exchange.name()) {
Some(existing) if *existing != declared => {
return Err(refused(format!(
"exchange {:?} is declared again with other settings ({declared:?}, where it \
was {existing:?})",
exchange.name()
)));
}
Some(_) => {}
None => {
routes
.exchanges
.insert(exchange.name().to_owned(), declared);
}
}
}
convert::short(&spec.name, "queue name")?;
if !spec.durable && !spec.exclusive {
return Err(refused(format!(
"queue {:?} is transient and not exclusive, which RabbitMQ 4 does not permit; declare \
it durable, or exclusive to the connection",
spec.name
)));
}
if spec.kind == QueueKind::Quorum
&& arguments
.inner()
.contains_key(&ShortString::from(MAX_PRIORITY))
{
return Err(refused(format!(
"quorum queue {:?} is declared with `{MAX_PRIORITY}`, an argument a quorum queue does \
not take",
spec.name
)));
}
let declared = DeclaredQueue {
kind: spec.kind,
durable: spec.durable,
exclusive: spec.exclusive,
auto_delete: spec.auto_delete,
arguments: arguments.clone(),
};
match routes.queues.get(&spec.name) {
Some(existing) if *existing != declared => Err(refused(format!(
"queue {:?} is declared again with other settings, which the server refuses as \
inequivalent",
spec.name
))),
Some(_) => Ok(()),
None => {
routes.queues.insert(spec.name.clone(), declared);
Ok(())
}
}
}
fn refused(reason: String) -> AmqpError {
AmqpError::Declare(reason.into())
}