use std::collections::BTreeMap;
use std::sync::{Arc, Mutex, MutexGuard};
use std::time::Duration;
use futures::Stream;
use lapin::types::{AMQPValue, FieldTable, ShortString};
use ruststream::testing::Coordinator;
use ruststream::{AckError, Subscriber};
use super::bus::{Bus, BusDelivery, ConsumerId, DeliveryReceiver};
use crate::convert;
use crate::delay::counted_again;
use crate::error::AmqpError;
use crate::message::LapinMessage;
use crate::queue::{
DEAD_LETTER_EXCHANGE, DEAD_LETTER_ROUTING_KEY, DELIVERY_LIMIT, QueueKind, QueueSpec,
};
const QUORUM_DEFAULT_DELIVERY_LIMIT: u64 = 20;
#[derive(Debug, Clone, Default)]
pub(crate) struct QueueBehaviour {
pub(crate) delays: bool,
pub(crate) counts: bool,
pub(crate) delivery_limit: Option<u64>,
pub(crate) dead_letter: Option<(String, Option<String>)>,
}
impl QueueBehaviour {
pub(crate) fn of(spec: &QueueSpec, arguments: &FieldTable) -> Self {
let counts = spec.kind == QueueKind::Quorum;
Self {
delays: spec.delay.is_some(),
counts,
delivery_limit: counts
.then(|| {
arguments
.inner()
.get(&ShortString::from(DELIVERY_LIMIT))
.map_or(Some(QUORUM_DEFAULT_DELIVERY_LIMIT), convert::counter)
})
.flatten(),
dead_letter: argument(arguments, DEAD_LETTER_EXCHANGE)
.map(|exchange| (exchange, argument(arguments, DEAD_LETTER_ROUTING_KEY))),
}
}
}
impl QueueBehaviour {
pub(crate) fn spend(&self, delivery: &mut BusDelivery) -> bool {
if !self.counts {
return false;
}
let spent = delivery.returns.unwrap_or(0).saturating_add(1);
if self.delivery_limit.is_some_and(|limit| spent > limit) {
return true;
}
delivery.returns = Some(spent);
false
}
pub(crate) fn dead_letter(&self, bus: &Bus, delivery: &BusDelivery) {
let Some((exchange, routing_key)) = &self.dead_letter else {
return;
};
let routing_key = routing_key.as_deref().unwrap_or(&delivery.routing_key);
bus.reroute(exchange, routing_key, delivery);
}
}
fn argument(arguments: &FieldTable, name: &str) -> Option<String> {
match arguments.inner().get(&ShortString::from(name))? {
AMQPValue::LongString(value) => Some(value.to_string()),
AMQPValue::ShortString(value) => Some(value.to_string()),
_ => None,
}
}
pub(crate) struct BusDeliveries {
bus: Arc<Bus>,
id: ConsumerId,
queue: String,
receiver: DeliveryReceiver,
next_tag: u64,
behaviour: Arc<QueueBehaviour>,
unsettled: Arc<Unsettled>,
}
#[derive(Debug, Default)]
pub(crate) struct Unsettled(Mutex<Option<BTreeMap<u64, BusDelivery>>>);
impl Unsettled {
fn open() -> Self {
Self(Mutex::new(Some(BTreeMap::new())))
}
fn lock(&self) -> MutexGuard<'_, Option<BTreeMap<u64, BusDelivery>>> {
self.0
.lock()
.expect("in-process unsettled deliveries mutex poisoned")
}
fn hand_out(&self, tag: u64, delivery: BusDelivery) {
if let Some(held) = self.lock().as_mut() {
held.insert(tag, delivery);
}
}
fn settle(&self, tag: u64) -> bool {
self.lock()
.as_mut()
.is_some_and(|held| held.remove(&tag).is_some())
}
fn close(&self) -> Vec<BusDelivery> {
self.lock()
.take()
.map(|held| held.into_values().collect())
.unwrap_or_default()
}
}
impl BusDeliveries {
pub(crate) fn open(
bus: &Arc<Bus>,
queue: String,
auto_delete: bool,
behaviour: QueueBehaviour,
) -> Self {
let (id, receiver) = bus.consume_queue(&queue, auto_delete);
Self {
bus: Arc::clone(bus),
id,
queue,
receiver,
next_tag: 1,
behaviour: Arc::new(behaviour),
unsettled: Arc::new(Unsettled::open()),
}
}
}
impl Drop for BusDeliveries {
fn drop(&mut self) {
self.bus.cancel(self.id);
for delivery in self.unsettled.close() {
self.bus
.requeue_unsettled(&self.queue, delivery, &self.behaviour);
}
self.receiver.close();
while let Ok(delivery) = self.receiver.try_recv() {
self.bus
.requeue_unread(&self.queue, delivery, &self.behaviour);
}
}
}
impl std::fmt::Debug for BusDeliveries {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BusDeliveries")
.field("queue", &self.queue)
.finish_non_exhaustive()
}
}
impl Subscriber for BusDeliveries {
type Message = LapinMessage;
type Error = AmqpError;
fn stream(&mut self) -> impl Stream<Item = Result<Self::Message, Self::Error>> + Send + '_ {
let Self {
bus,
receiver,
queue,
next_tag,
behaviour,
unsettled,
..
} = self;
futures::stream::poll_fn(move |cx| {
receiver.poll_recv(cx).map(|delivery| {
delivery.map(|delivery| {
let tag = *next_tag;
*next_tag += 1;
unsettled.hand_out(tag, delivery.clone());
Ok(LapinMessage::in_process(
delivery,
tag,
Settlement {
bus: Arc::clone(bus),
queue: queue.clone(),
behaviour: Arc::clone(behaviour),
coordinator: bus.coordinator(),
held: Some((Arc::clone(unsettled), tag)),
},
))
})
})
})
}
}
pub(crate) struct Settlement {
bus: Arc<Bus>,
queue: String,
behaviour: Arc<QueueBehaviour>,
coordinator: Option<Coordinator>,
held: Option<(Arc<Unsettled>, u64)>,
}
impl Settlement {
pub(crate) fn of_reply(bus: &Arc<Bus>, queue: String) -> Self {
Self {
bus: Arc::clone(bus),
queue,
behaviour: Arc::new(QueueBehaviour::default()),
coordinator: bus.coordinator(),
held: None,
}
}
const fn no_ack(&self) -> bool {
self.held.is_none()
}
pub(crate) fn delays(&self) -> bool {
self.behaviour.delays
}
fn open(&self, what: &str) -> Result<(), AckError> {
if self.bus.is_closed() {
return Err(AckError::Broker(
format!("{what} was not sent: the connection is closed").into(),
));
}
if let Some((unsettled, tag)) = &self.held
&& !unsettled.settle(*tag)
{
return Err(AckError::Broker(
format!("{what} was not sent: the consumer's channel is closed").into(),
));
}
Ok(())
}
pub(crate) fn ack(&self) -> Result<(), AckError> {
if self.no_ack() {
return Ok(());
}
self.open("basic.ack")
}
pub(crate) fn reject(&self, mut delivery: BusDelivery, requeue: bool) -> Result<(), AckError> {
if self.no_ack() {
return Ok(());
}
self.open("basic.reject")?;
if !requeue {
self.dead_letter(&delivery);
return Ok(());
}
if self.behaviour.spend(&mut delivery) {
self.dead_letter(&delivery);
return Ok(());
}
delivery.redelivered = true;
self.bus.deliver(&self.queue, delivery);
Ok(())
}
pub(crate) fn nack_after(
&self,
delivery: BusDelivery,
delay: Duration,
) -> Result<(), AckError> {
if !self.behaviour.delays || self.no_ack() {
return Err(AckError::Unsupported);
}
self.open("basic.ack")?;
let options = convert::redelivery_options(&delivery.headers);
let headers = convert::properties_for_publish(&counted_again(&delivery.headers), &options)
.map(|properties| convert::headers_from_properties(&properties))
.map_err(|err| AckError::Broker(Box::new(err)))?;
let copy = BusDelivery {
headers,
exchange: String::new(),
routing_key: self.queue.clone(),
redelivered: false,
returns: None,
..delivery
};
let bus = Arc::clone(&self.bus);
let queue = self.queue.clone();
let redeliver = move || bus.deliver(&queue, copy);
match self.coordinator.as_ref() {
Some(coordinator) => coordinator.schedule_redelivery(delay, redeliver),
None => {
self.bus.runtime().spawn(async move {
tokio::time::sleep(delay).await;
redeliver();
});
}
}
Ok(())
}
fn dead_letter(&self, delivery: &BusDelivery) {
self.behaviour.dead_letter(&self.bus, delivery);
}
}
impl Drop for Settlement {
fn drop(&mut self) {
if let Some(coordinator) = self.coordinator.take() {
coordinator.consumed();
}
}
}
impl std::fmt::Debug for Settlement {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Settlement")
.field("queue", &self.queue)
.finish_non_exhaustive()
}
}