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 it waits in the hold queue for its delay,
48    /// exactly as a retry does, so different delays never block each other. Use
49    /// [`Producer::defer`] when the job must come back *ahead* of the backlog.
50    ///
51    /// On RabbitMQ this needs the queue to have been declared through the same backend
52    /// (which [`Producer::new`] does and [`Producer::new_undeclared`] does not), and the
53    /// delay is capped at about 24.8 days; see the backend's docs.
54    pub async fn enqueue_after<J: Job<Queue = Q>>(
55        &self,
56        job: &J,
57        delay: Duration,
58    ) -> Result<uuid::Uuid> {
59        self.enqueue_delayed(job, Some(delay)).await
60    }
61
62    /// Publish `job` into a hold that releases it after `delay`, at the front of the
63    /// queue. Returns the job id.
64    ///
65    /// The envelope is a first-attempt one (`attempt = 1`, `deferrals = 0`) carrying
66    /// the highest priority its queue supports
67    /// ([`crate::QueueConfig::max_priority`], `0` when the queue is not a priority
68    /// queue), so when the delay is up it runs before everything that was enqueued
69    /// normally in the meantime. The producer-side twin of a handler returning
70    /// [`crate::JobError::Deferred`].
71    ///
72    /// Contrast with [`Producer::enqueue_after`]: that returns at priority `0`, behind
73    /// the backlog; this one returns at the top. Both wait in a hold per delay, so
74    /// equal delays drain strictly in order and different delays never block each
75    /// other.
76    pub async fn defer<J: Job<Queue = Q>>(&self, job: &J, delay: Duration) -> Result<uuid::Uuid> {
77        let mut env = Envelope::new(job)?;
78        env.priority = J::QUEUE.config().max_priority.unwrap_or(0);
79        self.backend.defer(&env, delay).await?;
80        Ok(env.job_id)
81    }
82
83    async fn enqueue_delayed<J: Job<Queue = Q>>(
84        &self,
85        job: &J,
86        delay: Option<Duration>,
87    ) -> Result<uuid::Uuid> {
88        let env = Envelope::new(job)?;
89        self.backend.publish(&env, delay).await?;
90        Ok(env.job_id)
91    }
92
93    /// The backend this producer publishes through.
94    pub fn backend(&self) -> &Arc<B> {
95        &self.backend
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102    use crate::{
103        memory::MemoryBackend,
104        test_support::{Greet, Nudge, TestQueues},
105    };
106
107    async fn producer() -> (Arc<MemoryBackend>, Producer<TestQueues, MemoryBackend>) {
108        let backend = Arc::new(MemoryBackend::new());
109        let producer = Producer::<TestQueues, _>::new(backend.clone())
110            .await
111            .unwrap();
112        (backend, producer)
113    }
114
115    #[tokio::test(start_paused = true)]
116    async fn defer_holds_the_job_and_returns_it_at_the_queue_top_priority() {
117        let (backend, producer) = producer().await;
118        // Two normal jobs are already waiting.
119        producer.enqueue(&Greet::new("backlog")).await.unwrap();
120
121        let id = producer
122            .defer(&Greet::new("held"), Duration::from_secs(30))
123            .await
124            .unwrap();
125
126        assert_eq!(backend.deferred("test.alpha"), 1);
127        assert_eq!(backend.pending("test.alpha"), 1, "only the backlog so far");
128
129        tokio::time::sleep(Duration::from_secs(29)).await;
130        assert_eq!(backend.pending("test.alpha"), 1);
131
132        tokio::time::sleep(Duration::from_secs(2)).await;
133        assert_eq!(backend.deferred("test.alpha"), 0);
134        assert_eq!(backend.pending("test.alpha"), 2);
135
136        let mut stream = backend.consume(&TestQueues::Alpha.config()).await.unwrap();
137        let first = futures::StreamExt::next(&mut stream)
138            .await
139            .unwrap()
140            .unwrap();
141        let envelope = first.envelope().clone();
142        assert_eq!(envelope.job_id, id, "the deferred job is served first");
143        // Alpha keeps the default ten priority levels.
144        assert_eq!(envelope.priority, 10);
145        assert_eq!(envelope.deferrals, 0, "the producer never deferred it once");
146        assert_eq!(envelope.attempt, 1);
147        first.ack().await.unwrap();
148    }
149
150    #[tokio::test(start_paused = true)]
151    async fn defer_on_a_queue_without_priorities_uses_zero() {
152        let (backend, producer) = producer().await;
153        producer
154            .defer(&Nudge { id: 1 }, Duration::from_secs(5))
155            .await
156            .unwrap();
157
158        tokio::time::sleep(Duration::from_secs(6)).await;
159        let mut stream = backend.consume(&TestQueues::Gamma.config()).await.unwrap();
160        let delivery = futures::StreamExt::next(&mut stream)
161            .await
162            .unwrap()
163            .unwrap();
164        assert_eq!(delivery.envelope().priority, 0);
165        assert_eq!(delivery.envelope().deferrals, 0);
166        delivery.ack().await.unwrap();
167    }
168
169    #[tokio::test]
170    async fn defer_on_a_closed_backend_fails() {
171        let (backend, producer) = producer().await;
172        backend.close().await.unwrap();
173        assert!(matches!(
174            producer
175                .defer(&Greet::new("x"), Duration::from_secs(1))
176                .await,
177            Err(crate::error::Error::ShutDown)
178        ));
179    }
180}