1use std::{
4 sync::{
5 Arc,
6 atomic::{AtomicU64, Ordering},
7 },
8 time::{Duration, SystemTime, UNIX_EPOCH},
9};
10
11use async_trait::async_trait;
12use futures::StreamExt;
13use lapin::{
14 Channel, Connection, Consumer,
15 message::Delivery as LapinDelivery,
16 options::{
17 BasicAckOptions, BasicConsumeOptions, BasicQosOptions, BasicRejectOptions,
18 QueueDeclareOptions,
19 },
20 types::{FieldTable, MAX_SHORT_STRING_LENGTH},
21};
22use queuey_core::{Backend, Delivery, DeliveryStream, Envelope, QueueConfig, Result};
23use tracing::{debug, error, info, warn};
24
25use crate::{
26 codec,
27 delivery::RabbitMqDelivery,
28 error::{RabbitMqError, amqp, short_string},
29 options::RabbitMqOptions,
30 publisher::Publisher,
31 topology,
32};
33
34const REPLY_SUCCESS: u16 = 200;
36
37#[derive(Debug)]
63pub struct RabbitMqBackend {
64 connection: Arc<Connection>,
65 publisher: Publisher,
66 options: Arc<RabbitMqOptions>,
67}
68
69impl RabbitMqBackend {
70 pub async fn connect(uri: &str) -> Result<Self> {
80 Self::with_options(uri, RabbitMqOptions::default()).await
81 }
82
83 pub async fn with_options(uri: &str, options: RabbitMqOptions) -> Result<Self> {
85 let connection = Arc::new(
86 Connection::connect(uri, options.connection_properties.clone())
87 .await
88 .map_err(amqp)?,
89 );
90
91 let channel = Publisher::open_confirm_channel(&connection).await?;
92 let declare_channel = connection.create_channel().await.map_err(amqp)?;
96
97 let options = Arc::new(options);
98 info!(
99 channel = channel.id(),
100 declare_channel = declare_channel.id(),
101 "rabbitmq backend connected"
102 );
103
104 Ok(Self {
105 publisher: Publisher::new(
106 Arc::clone(&connection),
107 channel,
108 declare_channel,
109 Arc::clone(&options),
110 ),
111 connection,
112 options,
113 })
114 }
115
116 #[must_use]
118 pub fn options(&self) -> &RabbitMqOptions {
119 &self.options
120 }
121
122 #[must_use]
124 pub fn retry_queue_name(&self, queue: &str) -> String {
125 topology::retry_queue_name(queue, &self.options.retry_suffix)
126 }
127
128 #[must_use]
130 pub fn dead_queue_name(&self, queue: &str) -> String {
131 topology::dead_queue_name(queue, &self.options.dead_suffix)
132 }
133
134 #[must_use]
140 pub fn deferred_queue_name(&self, queue: &str, ttl_ms: u32) -> String {
141 topology::deferred_queue_name(queue, &self.options.deferred_suffix, ttl_ms)
142 }
143
144 fn declare_options(durable: bool) -> QueueDeclareOptions {
146 topology::declare_options(durable)
147 }
148
149 async fn declare_one(&self, channel: &Channel, config: &QueueConfig) -> Result<()> {
155 channel
156 .queue_declare(
157 short_string(&config.name)?,
158 Self::declare_options(config.durable),
159 topology::queue_args(config),
160 )
161 .await
162 .map_err(amqp)?;
163
164 let retry = self.retry_queue_name(&config.name);
165 channel
166 .queue_declare(
167 short_string(&retry)?,
168 Self::declare_options(config.durable),
169 topology::retry_queue_args(config),
170 )
171 .await
172 .map_err(amqp)?;
173
174 if self.options.declare_dead_letter_queues {
175 let dead = self.dead_queue_name(&config.name);
176 channel
177 .queue_declare(
178 short_string(&dead)?,
181 Self::declare_options(true),
182 topology::dead_queue_args(config),
183 )
184 .await
185 .map_err(amqp)?;
186 }
187
188 self.publisher.remember(config);
192
193 debug!(queue = %config.name, "topology declared");
194 Ok(())
195 }
196}
197
198#[async_trait]
199impl Backend for RabbitMqBackend {
200 async fn declare(&self, queues: &[QueueConfig]) -> Result<()> {
201 if queues.is_empty() {
202 return Ok(());
203 }
204 for config in queues {
209 check_deferrable_name(&config.name, &self.options.deferred_suffix)?;
210 }
211 let channel = self.connection.create_channel().await.map_err(amqp)?;
212 let result: Result<()> = async {
213 for config in queues {
214 self.declare_one(&channel, config).await?;
215 }
216 Ok(())
217 }
218 .await;
219
220 if channel.status().connected()
222 && let Err(error) = channel.close(REPLY_SUCCESS, "OK".into()).await
223 {
224 debug!(%error, "closing the declaration channel failed");
225 }
226 result
227 }
228
229 async fn publish(&self, envelope: &Envelope, delay: Option<Duration>) -> Result<()> {
230 self.publisher.publish_envelope(envelope, delay).await
231 }
232
233 async fn defer(&self, envelope: &Envelope, delay: Duration) -> Result<()> {
260 self.publisher.publish_deferred(envelope, delay).await
261 }
262
263 async fn consume(&self, queue: &QueueConfig) -> Result<DeliveryStream> {
264 let channel = self.connection.create_channel().await.map_err(amqp)?;
265 channel
266 .basic_qos(queue.prefetch, BasicQosOptions { global: false })
267 .await
268 .map_err(amqp)?;
269
270 let tag = consumer_tag(&queue.name);
271 let consumer = channel
272 .basic_consume(
273 short_string(&queue.name)?,
274 short_string(&tag)?,
275 BasicConsumeOptions {
276 no_local: false,
277 no_ack: false,
278 exclusive: false,
279 nowait: false,
280 },
281 FieldTable::default(),
282 )
283 .await
284 .map_err(amqp)?;
285
286 info!(queue = %queue.name, prefetch = queue.prefetch, %tag, "consuming");
287
288 let stream: DeliveryStream = Box::pin(delivery_stream(ConsumeState {
289 consumer,
290 _channel: channel,
293 publisher: self.publisher.clone(),
294 queue: queue.name.clone(),
295 }));
296 Ok(stream)
297 }
298
299 async fn close(&self) -> Result<()> {
300 if let Err(error) = self.publisher.close().await {
301 debug!(%error, "closing the publishing channel failed");
302 }
303 if self.connection.status().connected() {
304 match self.connection.close(REPLY_SUCCESS, "OK".into()).await {
305 Ok(()) => {}
306 Err(error) if is_benign_close_error(&error) => {
307 debug!(%error, "connection already closing; treating close as successful");
308 }
309 Err(error) => return Err(amqp(error)),
310 }
311 }
312 info!("rabbitmq backend closed");
313 Ok(())
314 }
315}
316
317fn check_deferrable_name(queue: &str, deferred_suffix: &str) -> Result<()> {
327 let hold = topology::deferred_queue_name(queue, deferred_suffix, topology::MAX_DEFERRAL_MS);
328 if hold.len() <= MAX_SHORT_STRING_LENGTH {
329 return Ok(());
330 }
331 Err(RabbitMqError::DeferredNameTooLong {
332 queue: queue.to_owned(),
333 length: hold.len(),
334 hold,
335 limit: MAX_SHORT_STRING_LENGTH,
336 }
337 .into_core())
338}
339
340pub(crate) fn is_benign_close_error(error: &lapin::Error) -> bool {
349 use lapin::{ChannelState, ConnectionState, ErrorKind};
350 matches!(
351 error.kind(),
352 ErrorKind::InvalidChannelState(ChannelState::Closing | ChannelState::Closed, _)
353 | ErrorKind::InvalidConnectionState(ConnectionState::Closing | ConnectionState::Closed)
354 )
355}
356
357struct ConsumeState {
359 consumer: Consumer,
360 _channel: Channel,
361 publisher: Publisher,
362 queue: String,
363}
364
365fn delivery_stream(
371 state: ConsumeState,
372) -> impl futures::Stream<Item = Result<Box<dyn Delivery>>> + Send {
373 futures::stream::unfold(state, |mut state| async move {
374 loop {
375 let next = state.consumer.next().await?;
376 let delivery = match next {
377 Ok(delivery) => delivery,
378 Err(error) => return Some((Err(amqp(error)), state)),
381 };
382
383 match Envelope::from_bytes(&delivery.data) {
384 Ok(envelope) => {
385 let boxed: Box<dyn Delivery> = Box::new(RabbitMqDelivery::new(
386 envelope,
387 delivery.acker.clone(),
388 state.publisher.clone(),
389 ));
390 return Some((Ok(boxed), state));
391 }
392 Err(error) => {
393 warn!(
394 queue = %state.queue,
395 delivery_tag = delivery.delivery_tag,
396 bytes = delivery.data.len(),
397 %error,
398 "dropping message whose body is not a valid envelope"
399 );
400 discard_malformed(&state.publisher, &state.queue, &delivery).await;
401 }
402 }
403 }
404 })
405}
406
407async fn discard_malformed(publisher: &Publisher, queue: &str, delivery: &LapinDelivery) {
426 if publisher.options().declare_dead_letter_queues {
427 match publisher
428 .publish_malformed(queue, &delivery.data, codec::REASON_MALFORMED)
429 .await
430 {
431 Ok(()) => match delivery.acker.ack(BasicAckOptions::default()).await {
432 Ok(true) => return,
433 Ok(false) => {
434 warn!(
435 queue,
436 delivery_tag = delivery.delivery_tag,
437 "a malformed message was already settled; nothing left to ack"
438 );
439 return;
440 }
441 Err(error) => {
442 warn!(%error, queue, delivery_tag = delivery.delivery_tag,
443 "acking a malformed message failed; falling back to reject");
444 }
445 },
446 Err(error) => {
447 warn!(%error, queue, "forwarding a malformed message to the dead-letter queue failed");
448 }
449 }
450 }
451
452 match delivery
453 .acker
454 .reject(BasicRejectOptions { requeue: false })
455 .await
456 {
457 Ok(true) => {}
458 Ok(false) => {
459 warn!(
460 queue,
461 delivery_tag = delivery.delivery_tag,
462 "a malformed message was already settled; nothing left to reject"
463 );
464 }
465 Err(error) => {
466 error!(
467 %error, queue, delivery_tag = delivery.delivery_tag,
468 "rejecting a malformed message failed; it stays unacknowledged and holds a \
469 prefetch slot until the consumer channel closes"
470 );
471 }
472 }
473}
474
475fn consumer_tag(queue: &str) -> String {
481 static NEXT: AtomicU64 = AtomicU64::new(0);
482 let sequence = NEXT.fetch_add(1, Ordering::Relaxed);
483 let nanos = SystemTime::now()
484 .duration_since(UNIX_EPOCH)
485 .map(|since| since.as_nanos())
486 .unwrap_or_default();
487 let queue = codec::truncate_at_boundary(queue, 160);
489 format!("queuey.{queue}.{nanos:x}.{sequence}")
490}
491
492#[cfg(test)]
493mod tests {
494 use super::*;
495
496 #[test]
497 fn closing_state_errors_are_benign_on_close() {
498 use lapin::{ChannelState, ConnectionState, ErrorKind};
499 for kind in [
500 ErrorKind::InvalidChannelState(ChannelState::Closing, "channel.close"),
501 ErrorKind::InvalidChannelState(ChannelState::Closed, "channel.close"),
502 ErrorKind::InvalidConnectionState(ConnectionState::Closing),
503 ErrorKind::InvalidConnectionState(ConnectionState::Closed),
504 ] {
505 assert!(is_benign_close_error(&lapin::Error::from(kind)));
506 }
507 }
508
509 #[test]
510 fn other_state_errors_are_not_benign_on_close() {
511 use lapin::{ChannelState, ConnectionState, ErrorKind};
512 for kind in [
513 ErrorKind::InvalidChannelState(ChannelState::Initial, "channel.close"),
514 ErrorKind::InvalidChannelState(ChannelState::Error, "channel.close"),
515 ErrorKind::InvalidConnectionState(ConnectionState::Error),
516 ErrorKind::InvalidChannel(7),
517 ] {
518 assert!(!is_benign_close_error(&lapin::Error::from(kind)));
519 }
520 }
521
522 #[test]
523 fn consumer_tags_are_unique_and_name_the_queue() {
524 let first = consumer_tag("myapp.emails");
525 let second = consumer_tag("myapp.emails");
526 assert_ne!(first, second);
527 assert!(first.starts_with("queuey.myapp.emails."));
528 assert!(second.starts_with("queuey.myapp.emails."));
529 }
530
531 #[test]
532 fn consumer_tags_fit_in_a_short_string() {
533 let tag = consumer_tag(&"q".repeat(1000));
534 assert!(
535 tag.len() <= MAX_SHORT_STRING_LENGTH,
536 "len was {}",
537 tag.len()
538 );
539 assert!(short_string(&tag).is_ok());
540 }
541
542 #[test]
543 fn a_queue_name_with_room_for_its_hold_queues_is_accepted() {
544 assert!(check_deferrable_name("myapp.emails", topology::DEFAULT_DEFERRED_SUFFIX).is_ok());
545 let longest = "q".repeat(MAX_SHORT_STRING_LENGTH - ".deferred.".len() - 10);
547 assert_eq!(longest.len(), 235);
548 assert!(check_deferrable_name(&longest, topology::DEFAULT_DEFERRED_SUFFIX).is_ok());
549 }
550
551 #[test]
552 fn a_queue_name_that_leaves_no_room_for_hold_queues_is_refused_at_declare() {
553 let name = "q".repeat(250);
557 assert!(
558 short_string(&name).is_ok(),
559 "the queue name itself is legal"
560 );
561
562 let error = check_deferrable_name(&name, topology::DEFAULT_DEFERRED_SUFFIX)
563 .expect_err("a name with no room for hold queues must be refused");
564 let text = error.to_string();
565 assert!(
566 text.contains("leaves no room for its hold queues"),
567 "{text}"
568 );
569 assert!(text.contains("270 bytes"), "{text}");
570 assert!(text.contains("255-byte"), "{text}");
571 }
572
573 #[test]
574 fn the_hold_queue_name_check_uses_the_configured_suffix() {
575 let name = "q".repeat(240);
576 assert!(check_deferrable_name(&name, topology::DEFAULT_DEFERRED_SUFFIX).is_err());
578 assert!(check_deferrable_name(&name, "-h").is_ok());
580 }
581
582 #[test]
583 fn declare_options_never_auto_delete() {
584 let durable = RabbitMqBackend::declare_options(true);
585 assert!(durable.durable);
586 assert!(!durable.auto_delete);
587 assert!(!durable.exclusive);
588 assert!(!durable.passive);
589 assert!(!durable.nowait);
590
591 let transient = RabbitMqBackend::declare_options(false);
592 assert!(!transient.durable);
593 assert!(!transient.auto_delete);
594 }
595}