Skip to main content

queuey_core/
producer.rs

1//! The [`Producer`]: type-safe publishing into a queue set.
2
3use std::{sync::Arc, time::Duration};
4
5use crate::{backend::Backend, envelope::Envelope, error::Result, job::Job, queue::QueueSet};
6
7/// Type-safe publisher. `Q` pins the producer to one queue set so a job from a
8/// different application cannot be enqueued by accident.
9pub struct Producer<Q: QueueSet, B: Backend> {
10    backend: Arc<B>,
11    _q: std::marker::PhantomData<fn(Q)>,
12}
13
14impl<Q: QueueSet, B: Backend> Clone for Producer<Q, B> {
15    fn clone(&self) -> Self {
16        Self {
17            backend: self.backend.clone(),
18            _q: std::marker::PhantomData,
19        }
20    }
21}
22
23impl<Q: QueueSet, B: Backend> Producer<Q, B> {
24    /// Create a producer and declare all queues in `Q`.
25    pub async fn new(backend: Arc<B>) -> Result<Self> {
26        let configs: Vec<_> = Q::all().iter().map(|q| q.config()).collect();
27        backend.declare(&configs).await?;
28        Ok(Self::new_undeclared(backend))
29    }
30
31    /// Create a producer without declaring queues (they must already exist).
32    pub fn new_undeclared(backend: Arc<B>) -> Self {
33        Self {
34            backend,
35            _q: std::marker::PhantomData,
36        }
37    }
38
39    /// Publish `job` to its statically-known queue. Returns the job id.
40    pub async fn enqueue<J: Job<Queue = Q>>(&self, job: &J) -> Result<uuid::Uuid> {
41        self.enqueue_delayed(job, None).await
42    }
43
44    /// Publish `job`, to become visible after `delay`.
45    ///
46    /// The plain delay: the job waits, then joins the back of the queue like any other
47    /// message (priority `0`). On RabbitMQ every delayed publish of a queue shares one
48    /// wait queue, so a message with a long delay sitting at its head holds up shorter
49    /// ones behind it. Use [`Producer::defer`] when the job must come back *ahead* of
50    /// the backlog, or when many different delays are in play.
51    pub async fn enqueue_after<J: Job<Queue = Q>>(
52        &self,
53        job: &J,
54        delay: Duration,
55    ) -> Result<uuid::Uuid> {
56        self.enqueue_delayed(job, Some(delay)).await
57    }
58
59    /// Publish `job` into a hold that releases it after `delay`, at the front of the
60    /// queue. Returns the job id.
61    ///
62    /// The envelope is a first-attempt one (`attempt = 1`, `deferrals = 0`) carrying
63    /// the highest priority its queue supports
64    /// ([`crate::QueueConfig::max_priority`], `0` when the queue is not a priority
65    /// queue), so when the delay is up it runs before everything that was enqueued
66    /// normally in the meantime. The producer-side twin of a handler returning
67    /// [`crate::JobError::Deferred`].
68    ///
69    /// Contrast with [`Producer::enqueue_after`]: that shares one wait queue per queue
70    /// (head-of-line blocking between different delays on RabbitMQ) and returns at
71    /// priority `0`; this one is held per delay (equal delays drain strictly in order)
72    /// and returns at the top.
73    pub async fn defer<J: Job<Queue = Q>>(&self, job: &J, delay: Duration) -> Result<uuid::Uuid> {
74        let mut env = Envelope::new(job)?;
75        env.priority = J::QUEUE.config().max_priority.unwrap_or(0);
76        self.backend.defer(&env, delay).await?;
77        Ok(env.job_id)
78    }
79
80    async fn enqueue_delayed<J: Job<Queue = Q>>(
81        &self,
82        job: &J,
83        delay: Option<Duration>,
84    ) -> Result<uuid::Uuid> {
85        let env = Envelope::new(job)?;
86        self.backend.publish(&env, delay).await?;
87        Ok(env.job_id)
88    }
89
90    /// The backend this producer publishes through.
91    pub fn backend(&self) -> &Arc<B> {
92        &self.backend
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99    use crate::{
100        memory::MemoryBackend,
101        test_support::{Greet, Nudge, TestQueues},
102    };
103
104    async fn producer() -> (Arc<MemoryBackend>, Producer<TestQueues, MemoryBackend>) {
105        let backend = Arc::new(MemoryBackend::new());
106        let producer = Producer::<TestQueues, _>::new(backend.clone())
107            .await
108            .unwrap();
109        (backend, producer)
110    }
111
112    #[tokio::test(start_paused = true)]
113    async fn defer_holds_the_job_and_returns_it_at_the_queue_top_priority() {
114        let (backend, producer) = producer().await;
115        // Two normal jobs are already waiting.
116        producer.enqueue(&Greet::new("backlog")).await.unwrap();
117
118        let id = producer
119            .defer(&Greet::new("held"), Duration::from_secs(30))
120            .await
121            .unwrap();
122
123        assert_eq!(backend.deferred("test.alpha"), 1);
124        assert_eq!(backend.pending("test.alpha"), 1, "only the backlog so far");
125
126        tokio::time::sleep(Duration::from_secs(29)).await;
127        assert_eq!(backend.pending("test.alpha"), 1);
128
129        tokio::time::sleep(Duration::from_secs(2)).await;
130        assert_eq!(backend.deferred("test.alpha"), 0);
131        assert_eq!(backend.pending("test.alpha"), 2);
132
133        let mut stream = backend.consume(&TestQueues::Alpha.config()).await.unwrap();
134        let first = futures::StreamExt::next(&mut stream)
135            .await
136            .unwrap()
137            .unwrap();
138        let envelope = first.envelope().clone();
139        assert_eq!(envelope.job_id, id, "the deferred job is served first");
140        // Alpha keeps the default ten priority levels.
141        assert_eq!(envelope.priority, 10);
142        assert_eq!(envelope.deferrals, 0, "the producer never deferred it once");
143        assert_eq!(envelope.attempt, 1);
144        first.ack().await.unwrap();
145    }
146
147    #[tokio::test(start_paused = true)]
148    async fn defer_on_a_queue_without_priorities_uses_zero() {
149        let (backend, producer) = producer().await;
150        producer
151            .defer(&Nudge { id: 1 }, Duration::from_secs(5))
152            .await
153            .unwrap();
154
155        tokio::time::sleep(Duration::from_secs(6)).await;
156        let mut stream = backend.consume(&TestQueues::Gamma.config()).await.unwrap();
157        let delivery = futures::StreamExt::next(&mut stream)
158            .await
159            .unwrap()
160            .unwrap();
161        assert_eq!(delivery.envelope().priority, 0);
162        assert_eq!(delivery.envelope().deferrals, 0);
163        delivery.ack().await.unwrap();
164    }
165
166    #[tokio::test]
167    async fn defer_on_a_closed_backend_fails() {
168        let (backend, producer) = producer().await;
169        backend.close().await.unwrap();
170        assert!(matches!(
171            producer
172                .defer(&Greet::new("x"), Duration::from_secs(1))
173                .await,
174            Err(crate::error::Error::ShutDown)
175        ));
176    }
177}