Advanced Projects

Concurrent Work Queue

A bounded concurrent priority work queue coordinates multiple producer and consumer threads using a single Mutex<QueueState<T>> paired with two condition variables (not_empty and not_full), enforcing backpressure when the queue reaches capacity and ordering queued items by priority before FIFO sequence number.

Producer-Consumer Synchronization State Machine

Producers: push(item, priority)           Consumers: pop() -> Option<T>
       |                                         |
       v                                         v
  Lock Mutex<State>                         Lock Mutex<State>
  While heap.len() == cap && !closed:       While heap.is_empty() && !closed:
    not_full.wait(guard)                      not_empty.wait(guard)
  Push into BinaryHeap                      Pop highest-priority item
  not_empty.notify_one()                    not_full.notify_one()
Condition Producer Behavior (push) Consumer Behavior (pop)
len == 0 and !closed Inserts item and calls not_empty.notify_one() Sleeps in not_empty.wait(guard) without spinning
len == capacity and !closed Sleeps in not_full.wait(guard) (backpressure) Pops highest-priority item and calls not_full.notify_one()
closed == true Immediately returns Err(item) Drains remaining items, then returns None

Complete Implementation

use std::cmp::Ordering;
use std::collections::BinaryHeap;
use std::sync::{Arc, Condvar, Mutex};
use std::thread;

struct PrioritizedItem<T> {
    priority: u8,
    seq: u64,
    item: T,
}

impl<T> PartialEq for PrioritizedItem<T> {
    fn eq(&self, other: &Self) -> bool {
        self.priority == other.priority && self.seq == other.seq
    }
}

impl<T> Eq for PrioritizedItem<T> {}

impl<T> PartialOrd for PrioritizedItem<T> {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl<T> Ord for PrioritizedItem<T> {
    fn cmp(&self, other: &Self) -> Ordering {
        // Higher `priority` first; ties broken by smaller `seq` (FIFO order)
        self.priority
            .cmp(&other.priority)
            .then_with(|| other.seq.cmp(&self.seq))
    }
}

struct QueueState<T> {
    heap: BinaryHeap<PrioritizedItem<T>>,
    next_seq: u64,
    closed: bool,
}

pub struct BoundedWorkQueue<T> {
    capacity: usize,
    state: Mutex<QueueState<T>>,
    not_empty: Condvar,
    not_full: Condvar,
}

impl<T> BoundedWorkQueue<T> {
    pub fn with_capacity(capacity: usize) -> Self {
        assert!(capacity > 0, "capacity must be non-zero");
        Self {
            capacity,
            state: Mutex::new(QueueState {
                heap: BinaryHeap::with_capacity(capacity),
                next_seq: 0,
                closed: false,
            }),
            not_empty: Condvar::new(),
            not_full: Condvar::new(),
        }
    }

    pub fn push(&self, item: T, priority: u8) -> Result<(), T> {
        let mut guard = self.state.lock().unwrap();
        while guard.heap.len() == self.capacity && !guard.closed {
            guard = self.not_full.wait(guard).unwrap();
        }
        if guard.closed {
            return Err(item);
        }
        let seq = guard.next_seq;
        guard.next_seq += 1;
        guard.heap.push(PrioritizedItem {
            priority,
            seq,
            item,
        });
        self.not_empty.notify_one();
        Ok(())
    }

    pub fn pop(&self) -> Option<(T, u8)> {
        let mut guard = self.state.lock().unwrap();
        while guard.heap.is_empty() && !guard.closed {
            guard = self.not_empty.wait(guard).unwrap();
        }
        let popped = guard.heap.pop().map(|entry| (entry.item, entry.priority));
        if popped.is_some() {
            self.not_full.notify_one();
        }
        popped
    }

    pub fn close(&self) {
        let mut guard = self.state.lock().unwrap();
        guard.closed = true;
        self.not_empty.notify_all();
        self.not_full.notify_all();
    }
}

fn main() {
    let queue = Arc::new(BoundedWorkQueue::<&'static str>::with_capacity(8));

    // Enqueue mixed-priority jobs before starting consumers to demonstrate deterministic ordering
    let jobs = [
        ("batch-backup", 10),
        ("payment-webhook", 90),
        ("audit-log", 10),
        ("fraud-alert", 99),
        ("invoice-pdf", 50),
        ("auth-token-refresh", 90),
    ];

    for (name, prio) in jobs {
        queue.push(name, prio).unwrap();
    }
    queue.close();

    let results = Arc::new(Mutex::new(Vec::new()));
    thread::scope(|s| {
        for worker_id in 0..2 {
            let q = Arc::clone(&queue);
            let out = Arc::clone(&results);
            s.spawn(move || {
                while let Some((job, prio)) = q.pop() {
                    out.lock().unwrap().push((worker_id, prio, job));
                }
            });
        }
    });

    let mut processed = results.lock().unwrap().clone();
    processed.sort_by(|a, b| b.1.cmp(&a.1));
    println!("Processed jobs in priority order:");
    for (worker_id, prio, job) in processed {
        println!("  [prio={:>2}] {:<20} (handled by worker {})", prio, job, worker_id);
    }
}
Processed jobs in priority order:
  [prio=99] fraud-alert          (handled by worker 0)
  [prio=90] payment-webhook      (handled by worker 0)
  [prio=90] auth-token-refresh   (handled by worker 1)
  [prio=50] invoice-pdf          (handled by worker 0)
  [prio=10] batch-backup         (handled by worker 1)
  [prio=10] audit-log            (handled by worker 0)