Skip to main content

oxidite_queue/
queue.rs

1use async_trait::async_trait;
2use std::collections::VecDeque;
3use std::sync::Arc;
4use tokio::sync::Mutex;
5use crate::job::{JobWrapper, JobStatus};
6use crate::stats::StatsTracker;
7use crate::Result;
8
9/// Queue backend trait
10#[async_trait]
11pub trait QueueBackend: Send + Sync {
12    /// Enqueue a job
13    async fn enqueue(&self, job: JobWrapper) -> Result<()>;
14    /// Dequeue the next available job
15    async fn dequeue(&self) -> Result<Option<JobWrapper>>;
16    /// Mark a job as completed
17    async fn complete(&self, job_id: &str) -> Result<()>;
18    /// Mark a job as failed
19    async fn fail(&self, job_id: &str, error: String) -> Result<()>;
20    /// Retry a job
21    async fn retry(&self, job: JobWrapper) -> Result<()>;
22    /// Move a job to the dead letter queue
23    async fn move_to_dead_letter(&self, job: JobWrapper) -> Result<()>;
24    /// List jobs in the dead letter queue
25    async fn list_dead_letter(&self) -> Result<Vec<JobWrapper>>;
26    /// Retry a job from the dead letter queue
27    async fn retry_from_dead_letter(&self, job_id: &str) -> Result<()>;
28    /// Clear all jobs from the queue
29    async fn clear(&self) -> Result<()>;
30}
31
32/// In-memory queue backend
33pub struct MemoryBackend {
34    queue: Arc<Mutex<VecDeque<JobWrapper>>>,
35    dead_letter: Arc<Mutex<Vec<JobWrapper>>>,
36}
37
38impl MemoryBackend {
39    /// Create a new in-memory queue backend
40    pub fn new() -> Self {
41        Self {
42            queue: Arc::new(Mutex::new(VecDeque::new())),
43            dead_letter: Arc::new(Mutex::new(Vec::new())),
44        }
45    }
46}
47
48impl Default for MemoryBackend {
49    fn default() -> Self {
50        Self::new()
51    }
52}
53
54#[async_trait]
55impl QueueBackend for MemoryBackend {
56    async fn enqueue(&self, mut job: JobWrapper) -> Result<()> {
57        job.status = JobStatus::Pending;
58        let mut queue = self.queue.lock().await;
59        
60        // Insert based on priority (higher priority first)
61        let pos = queue.iter().position(|j| j.priority < job.priority)
62            .unwrap_or(queue.len());
63        queue.insert(pos, job);
64        
65        Ok(())
66    }
67
68    async fn dequeue(&self) -> Result<Option<JobWrapper>> {
69        let mut queue = self.queue.lock().await;
70        
71        // Find first job that can be run now
72        let now = chrono::Utc::now().timestamp();
73        let pos = queue.iter().position(|j| {
74            j.status == JobStatus::Pending &&
75            j.scheduled_at.map(|t| t <= now).unwrap_or(true)
76        });
77
78        if let Some(pos) = pos {
79            let mut job = queue.remove(pos).unwrap();
80            job.status = JobStatus::Running;
81            job.attempts += 1;
82            Ok(Some(job))
83        } else {
84            Ok(None)
85        }
86    }
87
88    async fn complete(&self, _job_id: &str) -> Result<()> {
89        // In memory backend doesn't need to track completed jobs
90        Ok(())
91    }
92
93    async fn fail(&self, _job_id: &str, _error: String) -> Result<()> {
94        // In memory backend doesn't need to track failed jobs
95        Ok(())
96    }
97
98    async fn retry(&self, job: JobWrapper) -> Result<()> {
99        self.enqueue(job).await
100    }
101
102    async fn move_to_dead_letter(&self, mut job: JobWrapper) -> Result<()> {
103        job.status = JobStatus::DeadLetter;
104        let mut dlq = self.dead_letter.lock().await;
105        dlq.push(job);
106        Ok(())
107    }
108
109    async fn list_dead_letter(&self) -> Result<Vec<JobWrapper>> {
110        let dlq = self.dead_letter.lock().await;
111        Ok(dlq.clone())
112    }
113
114    async fn retry_from_dead_letter(&self, job_id: &str) -> Result<()> {
115        let mut dlq = self.dead_letter.lock().await;
116        if let Some(pos) = dlq.iter().position(|j| j.id == job_id) {
117            let mut job = dlq.remove(pos);
118            job.status = JobStatus::Pending;
119            job.attempts = 0;
120            job.error = None;
121            drop(dlq); // Release lock before enqueue
122            self.enqueue(job).await?;
123        }
124        Ok(())
125    }
126
127    async fn clear(&self) -> Result<()> {
128        let mut queue = self.queue.lock().await;
129        queue.clear();
130        Ok(())
131    }
132}
133
134/// Queue for managing jobs
135pub struct Queue {
136    backend: Arc<dyn QueueBackend>,
137    stats: StatsTracker,
138}
139
140impl Queue {
141    /// Create a new queue with the given backend
142    pub fn new(backend: Arc<dyn QueueBackend>) -> Self {
143        Self {
144            backend,
145            stats: StatsTracker::new(),
146        }
147    }
148
149    /// Create a new queue backed by an in-memory backend
150    pub fn memory() -> Self {
151        Self::new(Arc::new(MemoryBackend::new()))
152    }
153
154    /// Enqueue a job and return its ID
155    pub async fn enqueue(&self, job: JobWrapper) -> Result<String> {
156        let job_id = job.id.clone();
157        self.backend.enqueue(job).await?;
158        self.stats.increment_enqueued().await;
159        Ok(job_id)
160    }
161
162    /// Dequeue the next available job
163    pub async fn dequeue(&self) -> Result<Option<JobWrapper>> {
164        let job = self.backend.dequeue().await?;
165        if job.is_some() {
166            self.stats.mark_running().await;
167        }
168        Ok(job)
169    }
170
171    /// Mark a job as completed
172    pub async fn complete(&self, job_id: &str) -> Result<()> {
173        self.backend.complete(job_id).await?;
174        self.stats.increment_processed().await;
175        Ok(())
176    }
177
178    /// Mark a job as failed
179    pub async fn fail(&self, job_id: &str, error: String) -> Result<()> {
180        self.backend.fail(job_id, error).await?;
181        self.stats.increment_failed().await;
182        Ok(())
183    }
184
185    /// Retry a job
186    pub async fn retry(&self, job: JobWrapper) -> Result<()> {
187        self.backend.retry(job).await?;
188        self.stats.increment_retried().await;
189        Ok(())
190    }
191
192    /// Move a job to the dead letter queue
193    pub async fn move_to_dead_letter(&self, job: JobWrapper) -> Result<()> {
194        self.backend.move_to_dead_letter(job).await?;
195        self.stats.increment_dead_letter().await;
196        Ok(())
197    }
198
199    /// List jobs in the dead letter queue
200    pub async fn list_dead_letter(&self) -> Result<Vec<JobWrapper>> {
201        self.backend.list_dead_letter().await
202    }
203
204    /// Retry a job from the dead letter queue
205    pub async fn retry_from_dead_letter(&self, job_id: &str) -> Result<()> {
206        self.backend.retry_from_dead_letter(job_id).await
207    }
208
209    /// Clear all jobs from the queue
210    pub async fn clear(&self) -> Result<()> {
211        self.backend.clear().await?;
212        self.stats.reset().await;
213        Ok(())
214    }
215
216    /// Get current queue statistics
217    pub async fn get_stats(&self) -> crate::stats::QueueStats {
218        self.stats.get_stats().await
219    }
220}
221
222#[cfg(test)]
223mod tests {
224    use super::*;
225    use crate::job::Job;
226    use serde::{Deserialize, Serialize};
227
228    #[derive(Serialize, Deserialize)]
229    struct TestJob {
230        value: i32,
231    }
232
233    #[async_trait::async_trait]
234    impl Job for TestJob {
235        async fn perform(&self) -> crate::Result<()> {
236            Ok(())
237        }
238    }
239
240    #[tokio::test]
241    async fn test_enqueue_dequeue() {
242        let queue = Queue::memory();
243        let job = JobWrapper::new(&TestJob { value: 42 }).unwrap();
244        
245        queue.enqueue(job).await.unwrap();
246        let dequeued = queue.dequeue().await.unwrap();
247        
248        assert!(dequeued.is_some());
249    }
250}