Skip to main content

oximedia_workflow/
queue.rs

1//! Task queue implementation with priority support.
2
3use crate::task::{Task, TaskId, TaskPriority, TaskState};
4use dashmap::DashMap;
5use std::collections::BinaryHeap;
6use std::sync::Arc;
7use tokio::sync::{Notify, RwLock};
8use tracing::{debug, info};
9
10/// Priority queue item.
11#[derive(Debug, Clone)]
12struct QueueItem {
13    task: Task,
14    priority: TaskPriority,
15    sequence: u64,
16}
17
18impl PartialEq for QueueItem {
19    fn eq(&self, other: &Self) -> bool {
20        self.priority == other.priority && self.sequence == other.sequence
21    }
22}
23
24impl Eq for QueueItem {}
25
26impl PartialOrd for QueueItem {
27    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
28        Some(self.cmp(other))
29    }
30}
31
32impl Ord for QueueItem {
33    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
34        // First compare by priority (higher priority first)
35        match self.priority.cmp(&other.priority) {
36            std::cmp::Ordering::Equal => {
37                // Then by sequence (lower sequence first for FIFO within same priority)
38                other.sequence.cmp(&self.sequence)
39            }
40            ordering => ordering,
41        }
42    }
43}
44
45/// Task queue with priority support.
46pub struct TaskQueue {
47    /// Pending tasks ordered by priority.
48    pending: Arc<RwLock<BinaryHeap<QueueItem>>>,
49    /// Task lookup by ID.
50    tasks: Arc<DashMap<TaskId, Task>>,
51    /// Running tasks.
52    running: Arc<DashMap<TaskId, Task>>,
53    /// Sequence counter for FIFO ordering within same priority.
54    sequence: Arc<RwLock<u64>>,
55    /// Notification for new tasks.
56    notify: Arc<Notify>,
57    /// Maximum queue size (0 = unlimited).
58    max_size: usize,
59}
60
61impl TaskQueue {
62    /// Create a new task queue.
63    #[must_use]
64    pub fn new() -> Self {
65        Self {
66            pending: Arc::new(RwLock::new(BinaryHeap::new())),
67            tasks: Arc::new(DashMap::new()),
68            running: Arc::new(DashMap::new()),
69            sequence: Arc::new(RwLock::new(0)),
70            notify: Arc::new(Notify::new()),
71            max_size: 0,
72        }
73    }
74
75    /// Create a new task queue with maximum size.
76    #[must_use]
77    pub fn with_max_size(max_size: usize) -> Self {
78        Self {
79            pending: Arc::new(RwLock::new(BinaryHeap::new())),
80            tasks: Arc::new(DashMap::new()),
81            running: Arc::new(DashMap::new()),
82            sequence: Arc::new(RwLock::new(0)),
83            notify: Arc::new(Notify::new()),
84            max_size,
85        }
86    }
87
88    /// Enqueue a task.
89    pub async fn enqueue(&self, mut task: Task) -> crate::error::Result<()> {
90        // Check queue size limit
91        if self.max_size > 0 && self.len().await >= self.max_size {
92            return Err(crate::error::WorkflowError::ResourceLimitExceeded {
93                resource: "queue_size".to_string(),
94                limit: self.max_size.to_string(),
95            });
96        }
97
98        task.set_state(TaskState::Queued)?;
99
100        let priority = task.priority;
101        let task_id = task.id;
102
103        // Get sequence number
104        let mut seq = self.sequence.write().await;
105        let sequence = *seq;
106        *seq += 1;
107        drop(seq);
108
109        // Add to tasks map
110        self.tasks.insert(task_id, task.clone());
111
112        // Add to pending queue
113        let item = QueueItem {
114            task,
115            priority,
116            sequence,
117        };
118
119        self.pending.write().await.push(item);
120
121        debug!("Enqueued task {} with priority {:?}", task_id, priority);
122
123        // Notify waiting consumers
124        self.notify.notify_one();
125
126        Ok(())
127    }
128
129    /// Dequeue the highest priority task.
130    pub async fn dequeue(&self) -> Option<Task> {
131        loop {
132            // Try to get a task
133            let mut pending = self.pending.write().await;
134            if let Some(item) = pending.pop() {
135                drop(pending);
136
137                let mut task = item.task;
138                let task_id = task.id;
139
140                // Move to running
141                if task.set_state(TaskState::Running).is_ok() {
142                    self.running.insert(task_id, task.clone());
143                    debug!("Dequeued task {}", task_id);
144                    return Some(task);
145                }
146
147                // State transition failed, try next task
148                continue;
149            }
150
151            drop(pending);
152
153            // No tasks available, wait for notification
154            self.notify.notified().await;
155        }
156    }
157
158    /// Try to dequeue without blocking.
159    pub async fn try_dequeue(&self) -> Option<Task> {
160        let mut pending = self.pending.write().await;
161        while let Some(item) = pending.pop() {
162            let mut task = item.task;
163            let task_id = task.id;
164
165            if task.set_state(TaskState::Running).is_ok() {
166                self.running.insert(task_id, task.clone());
167                debug!("Dequeued task {}", task_id);
168                return Some(task);
169            }
170        }
171
172        None
173    }
174
175    /// Complete a task.
176    pub async fn complete(&self, task_id: TaskId, success: bool) {
177        if let Some((_, mut task)) = self.running.remove(&task_id) {
178            let new_state = if success {
179                TaskState::Completed
180            } else {
181                TaskState::Failed
182            };
183
184            if task.set_state(new_state).is_ok() {
185                self.tasks.insert(task_id, task);
186            }
187
188            info!("Task {} completed with success={}", task_id, success);
189        }
190    }
191
192    /// Get task by ID.
193    #[must_use]
194    pub fn get_task(&self, task_id: &TaskId) -> Option<Task> {
195        self.tasks.get(task_id).map(|t| t.clone())
196    }
197
198    /// Get running task by ID.
199    #[must_use]
200    pub fn get_running_task(&self, task_id: &TaskId) -> Option<Task> {
201        self.running.get(task_id).map(|t| t.clone())
202    }
203
204    /// Get queue length (pending + running).
205    pub async fn len(&self) -> usize {
206        let pending_count = self.pending.read().await.len();
207        let running_count = self.running.len();
208        pending_count + running_count
209    }
210
211    /// Check if queue is empty.
212    pub async fn is_empty(&self) -> bool {
213        self.len().await == 0
214    }
215
216    /// Get number of pending tasks.
217    pub async fn pending_count(&self) -> usize {
218        self.pending.read().await.len()
219    }
220
221    /// Get number of running tasks.
222    #[must_use]
223    pub fn running_count(&self) -> usize {
224        self.running.len()
225    }
226
227    /// Clear all tasks.
228    pub async fn clear(&self) {
229        self.pending.write().await.clear();
230        self.running.clear();
231        self.tasks.clear();
232        *self.sequence.write().await = 0;
233        info!("Queue cleared");
234    }
235
236    /// Get all pending tasks (for inspection).
237    pub async fn get_pending_tasks(&self) -> Vec<Task> {
238        self.pending
239            .read()
240            .await
241            .iter()
242            .map(|item| item.task.clone())
243            .collect()
244    }
245
246    /// Get all running tasks (for inspection).
247    #[must_use]
248    pub fn get_running_tasks(&self) -> Vec<Task> {
249        self.running
250            .iter()
251            .map(|entry| entry.value().clone())
252            .collect()
253    }
254
255    /// Remove a task from the queue (if not running).
256    pub async fn remove(&self, task_id: TaskId) -> bool {
257        if self.running.contains_key(&task_id) {
258            return false;
259        }
260
261        self.tasks.remove(&task_id);
262
263        // Rebuild pending queue without the task
264        let mut pending = self.pending.write().await;
265        let items: Vec<_> = pending
266            .drain()
267            .filter(|item| item.task.id != task_id)
268            .collect();
269
270        *pending = items.into_iter().collect();
271
272        true
273    }
274
275    /// Requeue a failed task for retry.
276    pub async fn requeue(&self, mut task: Task) -> crate::error::Result<()> {
277        // Remove from running
278        self.running.remove(&task.id);
279
280        // Increment retry count
281        task.increment_retry();
282
283        // Check if should retry
284        if !task.should_retry() {
285            task.set_state(TaskState::Failed)?;
286            self.tasks.insert(task.id, task);
287            return Ok(());
288        }
289
290        // Requeue with delay
291        task.set_state(TaskState::Retrying)?;
292        let delay = task.retry_delay();
293
294        debug!(
295            "Requeueing task {} after {:?} (attempt {})",
296            task.id,
297            delay,
298            task.retry_count + 1
299        );
300
301        let _task_id = task.id;
302        let queue = self.clone();
303
304        // Spawn delay and requeue
305        tokio::spawn(async move {
306            tokio::time::sleep(delay).await;
307            let _ = queue.enqueue(task).await;
308        });
309
310        Ok(())
311    }
312
313    /// Get queue statistics.
314    #[must_use]
315    pub async fn statistics(&self) -> QueueStatistics {
316        let pending = self.pending_count().await;
317        let running = self.running_count();
318        let total = self.tasks.len();
319
320        // Count by priority
321        let pending_tasks = self.get_pending_tasks().await;
322        let mut low = 0;
323        let mut normal = 0;
324        let mut high = 0;
325        let mut critical = 0;
326
327        for task in pending_tasks {
328            match task.priority {
329                TaskPriority::Low => low += 1,
330                TaskPriority::Normal => normal += 1,
331                TaskPriority::High => high += 1,
332                TaskPriority::Critical => critical += 1,
333            }
334        }
335
336        QueueStatistics {
337            total_tasks: total,
338            pending_tasks: pending,
339            running_tasks: running,
340            low_priority: low,
341            normal_priority: normal,
342            high_priority: high,
343            critical_priority: critical,
344        }
345    }
346}
347
348impl Clone for TaskQueue {
349    fn clone(&self) -> Self {
350        Self {
351            pending: self.pending.clone(),
352            tasks: self.tasks.clone(),
353            running: self.running.clone(),
354            sequence: self.sequence.clone(),
355            notify: self.notify.clone(),
356            max_size: self.max_size,
357        }
358    }
359}
360
361impl Default for TaskQueue {
362    fn default() -> Self {
363        Self::new()
364    }
365}
366
367/// Queue statistics.
368#[derive(Debug, Clone)]
369pub struct QueueStatistics {
370    /// Total tasks in queue.
371    pub total_tasks: usize,
372    /// Pending tasks.
373    pub pending_tasks: usize,
374    /// Running tasks.
375    pub running_tasks: usize,
376    /// Low priority tasks.
377    pub low_priority: usize,
378    /// Normal priority tasks.
379    pub normal_priority: usize,
380    /// High priority tasks.
381    pub high_priority: usize,
382    /// Critical priority tasks.
383    pub critical_priority: usize,
384}
385
386#[cfg(test)]
387mod tests {
388    use super::*;
389    use std::time::Duration;
390
391    fn create_test_task(name: &str, priority: TaskPriority) -> Task {
392        Task::new(
393            name,
394            crate::task::TaskType::Wait {
395                duration: Duration::from_secs(1),
396            },
397        )
398        .with_priority(priority)
399    }
400
401    #[tokio::test]
402    async fn test_queue_creation() {
403        let queue = TaskQueue::new();
404        assert_eq!(queue.pending_count().await, 0);
405        assert_eq!(queue.running_count(), 0);
406    }
407
408    #[tokio::test]
409    async fn test_enqueue_dequeue() {
410        let queue = TaskQueue::new();
411        let task = create_test_task("task1", TaskPriority::Normal);
412        let task_id = task.id;
413
414        queue.enqueue(task).await.expect("should succeed in test");
415        assert_eq!(queue.pending_count().await, 1);
416
417        let dequeued = queue.try_dequeue().await;
418        assert!(dequeued.is_some());
419        assert_eq!(dequeued.expect("should succeed in test").id, task_id);
420        assert_eq!(queue.running_count(), 1);
421    }
422
423    #[tokio::test]
424    async fn test_priority_ordering() {
425        let queue = TaskQueue::new();
426
427        let task_low = create_test_task("low", TaskPriority::Low);
428        let task_high = create_test_task("high", TaskPriority::High);
429        let task_normal = create_test_task("normal", TaskPriority::Normal);
430
431        queue
432            .enqueue(task_low)
433            .await
434            .expect("should succeed in test");
435        queue
436            .enqueue(task_high.clone())
437            .await
438            .expect("should succeed in test");
439        queue
440            .enqueue(task_normal)
441            .await
442            .expect("should succeed in test");
443
444        let dequeued = queue.try_dequeue().await.expect("should succeed in test");
445        assert_eq!(dequeued.id, task_high.id);
446    }
447
448    #[tokio::test]
449    async fn test_fifo_within_priority() {
450        let queue = TaskQueue::new();
451
452        let task1 = create_test_task("task1", TaskPriority::Normal);
453        let task2 = create_test_task("task2", TaskPriority::Normal);
454        let task3 = create_test_task("task3", TaskPriority::Normal);
455
456        let id1 = task1.id;
457
458        queue.enqueue(task1).await.expect("should succeed in test");
459        queue.enqueue(task2).await.expect("should succeed in test");
460        queue.enqueue(task3).await.expect("should succeed in test");
461
462        let dequeued = queue.try_dequeue().await.expect("should succeed in test");
463        assert_eq!(dequeued.id, id1);
464    }
465
466    #[tokio::test]
467    async fn test_complete_task() {
468        let queue = TaskQueue::new();
469        let task = create_test_task("task1", TaskPriority::Normal);
470        let task_id = task.id;
471
472        queue.enqueue(task).await.expect("should succeed in test");
473        queue.try_dequeue().await;
474
475        assert_eq!(queue.running_count(), 1);
476
477        queue.complete(task_id, true).await;
478        assert_eq!(queue.running_count(), 0);
479
480        let completed_task = queue.get_task(&task_id).expect("should succeed in test");
481        assert_eq!(completed_task.state, TaskState::Completed);
482    }
483
484    #[tokio::test]
485    async fn test_queue_max_size() {
486        let queue = TaskQueue::with_max_size(2);
487
488        let task1 = create_test_task("task1", TaskPriority::Normal);
489        let task2 = create_test_task("task2", TaskPriority::Normal);
490        let task3 = create_test_task("task3", TaskPriority::Normal);
491
492        assert!(queue.enqueue(task1).await.is_ok());
493        assert!(queue.enqueue(task2).await.is_ok());
494        assert!(queue.enqueue(task3).await.is_err());
495    }
496
497    #[tokio::test]
498    async fn test_remove_task() {
499        let queue = TaskQueue::new();
500        let task = create_test_task("task1", TaskPriority::Normal);
501        let task_id = task.id;
502
503        queue.enqueue(task).await.expect("should succeed in test");
504        assert_eq!(queue.pending_count().await, 1);
505
506        let removed = queue.remove(task_id).await;
507        assert!(removed);
508        assert_eq!(queue.pending_count().await, 0);
509    }
510
511    #[tokio::test]
512    async fn test_clear_queue() {
513        let queue = TaskQueue::new();
514
515        queue
516            .enqueue(create_test_task("task1", TaskPriority::Normal))
517            .await
518            .expect("should succeed in test");
519        queue
520            .enqueue(create_test_task("task2", TaskPriority::Normal))
521            .await
522            .expect("should succeed in test");
523
524        queue.clear().await;
525        assert_eq!(queue.pending_count().await, 0);
526    }
527
528    #[tokio::test]
529    async fn test_queue_statistics() {
530        let queue = TaskQueue::new();
531
532        queue
533            .enqueue(create_test_task("low", TaskPriority::Low))
534            .await
535            .expect("should succeed in test");
536        queue
537            .enqueue(create_test_task("normal", TaskPriority::Normal))
538            .await
539            .expect("should succeed in test");
540        queue
541            .enqueue(create_test_task("high", TaskPriority::High))
542            .await
543            .expect("should succeed in test");
544
545        let stats = queue.statistics().await;
546        assert_eq!(stats.pending_tasks, 3);
547        assert_eq!(stats.low_priority, 1);
548        assert_eq!(stats.normal_priority, 1);
549        assert_eq!(stats.high_priority, 1);
550    }
551
552    #[tokio::test]
553    async fn test_requeue() {
554        let queue = TaskQueue::new();
555        let mut task = create_test_task("task1", TaskPriority::Normal);
556        task.retry = crate::task::RetryPolicy {
557            max_attempts: 3,
558            initial_delay: Duration::from_millis(10),
559            max_delay: Duration::from_secs(1),
560            backoff_multiplier: 2.0,
561            exponential_backoff: true,
562        };
563
564        let _task_id = task.id;
565
566        queue.enqueue(task).await.expect("should succeed in test");
567        let task = queue.try_dequeue().await.expect("should succeed in test");
568
569        // Requeue for retry
570        queue.requeue(task).await.expect("should succeed in test");
571
572        // Wait for requeue delay — use a generous timeout so this test is
573        // robust under concurrent workspace-level test load (initial_delay is
574        // 10 ms; 500 ms gives 50× headroom for a loaded scheduler).
575        tokio::time::sleep(Duration::from_millis(500)).await;
576
577        // Task should be back in queue
578        assert!(queue.pending_count().await > 0 || queue.running_count() > 0);
579    }
580}