use std::sync::Arc;
use std::time::Duration;
use ruststream::{HeaderMap, OutgoingMessage};
use tokio::time::{Instant, timeout_at};
use tracing::debug;
use super::bus::{Bus, ConsumerId, DeliveryReceiver};
use super::deliveries::Settlement;
use crate::error::AmqpError;
use crate::message::LapinMessage;
use crate::publish_policy::PublishOptions;
use crate::requester::REPLY_TO;
struct Inbox {
bus: Arc<Bus>,
id: ConsumerId,
address: String,
correlation_id: String,
replies: DeliveryReceiver,
}
impl Inbox {
fn open(bus: &Arc<Bus>) -> Self {
let seq = bus.next_inbox();
let address = format!("{REPLY_TO}.{seq}");
let (id, replies) = bus.consume(address.clone());
Self {
bus: Arc::clone(bus),
id,
address,
correlation_id: format!("rs-{seq}"),
replies,
}
}
fn discard(&self) {
if let Some(coordinator) = self.bus.coordinator() {
coordinator.consumed();
}
}
}
impl Drop for Inbox {
fn drop(&mut self) {
self.bus.cancel(self.id);
while self.replies.try_recv().is_ok() {
self.discard();
}
}
}
pub(crate) async fn request(
bus: &Arc<Bus>,
options: &PublishOptions,
msg: OutgoingMessage<'_>,
timeout: Duration,
) -> Result<LapinMessage, AmqpError> {
bus.ensure_live(msg.name())?;
let mut inbox = Inbox::open(bus);
let mut headers: HeaderMap = msg.headers().clone();
headers.insert("reply-to", inbox.address.clone());
headers.insert("correlation-id", inbox.correlation_id.clone());
bus.publish(
&options.exchange,
msg.name(),
msg.payload(),
&headers,
&options.resolve(None),
)?;
let deadline = Instant::now() + timeout;
let mut tag = 1;
loop {
let Ok(received) = timeout_at(deadline, inbox.replies.recv()).await else {
return Err(AmqpError::RequestTimeout(timeout));
};
let Some(delivery) = received else {
return Err(AmqpError::closed(&inbox.address));
};
if delivery.headers.correlation_id() == Some(inbox.correlation_id.as_str()) {
let settlement = Settlement::of_reply(&inbox.bus, inbox.address.clone());
return Ok(LapinMessage::in_process(delivery, tag, settlement));
}
tag += 1;
debug!(
target: "ruststream_lapin",
address = inbox.address,
"dropping a reply that does not correlate with the pending request"
);
inbox.discard();
}
}