use std::cmp::Ordering;
use std::collections::BinaryHeap;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering as AtomicOrdering};
use std::time::{Duration, Instant};
use parking_lot::Mutex;
use tokio::sync::Semaphore;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[repr(u8)]
pub enum Priority {
Background = 0,
Low = 1,
#[default]
Normal = 2,
High = 3,
Critical = 4,
}
impl Priority {
pub fn value(&self) -> u8 {
*self as u8
}
pub fn weight(&self) -> u32 {
match self {
Self::Background => 1,
Self::Low => 2,
Self::Normal => 4,
Self::High => 8,
Self::Critical => 16,
}
}
pub fn max_wait(&self) -> Duration {
match self {
Self::Background => Duration::from_secs(60),
Self::Low => Duration::from_secs(30),
Self::Normal => Duration::from_secs(15),
Self::High => Duration::from_secs(5),
Self::Critical => Duration::from_secs(1),
}
}
}
#[derive(Debug)]
pub struct QueuedRequest<T> {
pub data: T,
pub priority: Priority,
pub queued_at: Instant,
sequence: u64,
}
impl<T> QueuedRequest<T> {
pub fn wait_time(&self) -> Duration {
self.queued_at.elapsed()
}
pub fn effective_priority(&self) -> Priority {
let wait = self.wait_time();
let max_wait = self.priority.max_wait();
if wait > max_wait * 4 {
Priority::Critical
} else if wait > max_wait * 2 {
match self.priority {
Priority::Background => Priority::Low,
Priority::Low => Priority::Normal,
Priority::Normal => Priority::High,
Priority::High | Priority::Critical => Priority::Critical,
}
} else if wait > max_wait {
match self.priority {
Priority::Background => Priority::Background,
Priority::Low => Priority::Low,
Priority::Normal => Priority::Normal,
Priority::High => Priority::Critical,
Priority::Critical => Priority::Critical,
}
} else {
self.priority
}
}
}
impl<T> Eq for QueuedRequest<T> {}
impl<T> PartialEq for QueuedRequest<T> {
fn eq(&self, other: &Self) -> bool {
self.sequence == other.sequence
}
}
impl<T> Ord for QueuedRequest<T> {
fn cmp(&self, other: &Self) -> Ordering {
let self_priority = self.effective_priority();
let other_priority = other.effective_priority();
match self_priority.cmp(&other_priority) {
Ordering::Equal => {
other.sequence.cmp(&self.sequence)
}
other => other,
}
}
}
impl<T> PartialOrd for QueuedRequest<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[derive(Debug, Clone)]
pub struct QueueConfig {
pub max_size: usize,
pub max_concurrent: usize,
pub enable_boosting: bool,
pub cleanup_interval: Duration,
pub max_queue_time: Duration,
}
impl Default for QueueConfig {
fn default() -> Self {
Self {
max_size: 10000,
max_concurrent: 100,
enable_boosting: true,
cleanup_interval: Duration::from_secs(10),
max_queue_time: Duration::from_secs(300),
}
}
}
impl QueueConfig {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_max_size(mut self, max: usize) -> Self {
self.max_size = max;
self
}
#[must_use]
pub fn with_max_concurrent(mut self, max: usize) -> Self {
self.max_concurrent = max;
self
}
#[must_use]
pub fn with_boosting(mut self, enabled: bool) -> Self {
self.enable_boosting = enabled;
self
}
}
#[derive(Debug)]
pub struct PriorityQueue<T> {
config: QueueConfig,
queue: Arc<Mutex<BinaryHeap<QueuedRequest<T>>>>,
sequence: AtomicU64,
semaphore: Arc<Semaphore>,
stats: Arc<QueueStats>,
}
impl<T: Send + 'static> PriorityQueue<T> {
pub fn new(config: QueueConfig) -> Self {
let max_concurrent = config.max_concurrent;
Self {
config,
queue: Arc::new(Mutex::new(BinaryHeap::new())),
sequence: AtomicU64::new(0),
semaphore: Arc::new(Semaphore::new(max_concurrent)),
stats: Arc::new(QueueStats::new()),
}
}
pub fn push(&self, data: T) -> Result<(), QueueError> {
self.push_with_priority(data, Priority::Normal)
}
pub fn push_with_priority(&self, data: T, priority: Priority) -> Result<(), QueueError> {
let mut queue = self.queue.lock();
if queue.len() >= self.config.max_size {
self.stats.rejected.fetch_add(1, AtomicOrdering::Relaxed);
return Err(QueueError::QueueFull);
}
let request = QueuedRequest {
data,
priority,
queued_at: Instant::now(),
sequence: self.sequence.fetch_add(1, AtomicOrdering::Relaxed),
};
queue.push(request);
self.stats.enqueued.fetch_add(1, AtomicOrdering::Relaxed);
self.stats.update_size(queue.len());
Ok(())
}
pub fn pop(&self) -> Option<QueuedRequest<T>> {
let mut queue = self.queue.lock();
let request = queue.pop();
if request.is_some() {
self.stats.dequeued.fetch_add(1, AtomicOrdering::Relaxed);
self.stats.update_size(queue.len());
}
request
}
pub async fn acquire(&self) -> Option<(QueuedRequest<T>, QueuePermit)> {
let permit = self.semaphore.clone().acquire_owned().await.ok()?;
let request = self.pop()?;
let wait_time = request.wait_time();
self.stats
.total_wait_ms
.fetch_add(wait_time.as_millis() as u64, AtomicOrdering::Relaxed);
Some((request, QueuePermit { _permit: permit }))
}
pub fn len(&self) -> usize {
self.queue.lock().len()
}
pub fn is_empty(&self) -> bool {
self.queue.lock().is_empty()
}
pub fn stats(&self) -> QueueStatistics {
let dequeued = self.stats.dequeued.load(AtomicOrdering::Relaxed);
let total_wait = self.stats.total_wait_ms.load(AtomicOrdering::Relaxed);
QueueStatistics {
current_size: self.len(),
enqueued: self.stats.enqueued.load(AtomicOrdering::Relaxed),
dequeued,
rejected: self.stats.rejected.load(AtomicOrdering::Relaxed),
max_size_reached: self.stats.max_size.load(AtomicOrdering::Relaxed),
avg_wait_ms: if dequeued > 0 {
total_wait / dequeued
} else {
0
},
}
}
pub fn cleanup(&self) {
let mut queue = self.queue.lock();
let max_time = self.config.max_queue_time;
let valid: Vec<_> = queue
.drain()
.filter(|r| r.queued_at.elapsed() < max_time)
.collect();
let removed = queue.len();
for item in valid {
queue.push(item);
}
if removed > 0 {
self.stats.update_size(queue.len());
}
}
pub fn clear(&self) {
let mut queue = self.queue.lock();
queue.clear();
self.stats.update_size(0);
}
}
#[derive(Debug)]
pub struct QueuePermit {
_permit: tokio::sync::OwnedSemaphorePermit,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum QueueError {
QueueFull,
Timeout,
Closed,
}
impl std::fmt::Display for QueueError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::QueueFull => write!(f, "queue is full"),
Self::Timeout => write!(f, "request timeout"),
Self::Closed => write!(f, "queue is closed"),
}
}
}
impl std::error::Error for QueueError {}
#[derive(Debug)]
struct QueueStats {
enqueued: AtomicU64,
dequeued: AtomicU64,
rejected: AtomicU64,
max_size: AtomicU64,
total_wait_ms: AtomicU64,
}
impl QueueStats {
fn new() -> Self {
Self {
enqueued: AtomicU64::new(0),
dequeued: AtomicU64::new(0),
rejected: AtomicU64::new(0),
max_size: AtomicU64::new(0),
total_wait_ms: AtomicU64::new(0),
}
}
fn update_size(&self, size: usize) {
let current_max = self.max_size.load(AtomicOrdering::Relaxed);
if size as u64 > current_max {
self.max_size.store(size as u64, AtomicOrdering::Relaxed);
}
}
}
#[derive(Debug, Clone)]
pub struct QueueStatistics {
pub current_size: usize,
pub enqueued: u64,
pub dequeued: u64,
pub rejected: u64,
pub max_size_reached: u64,
pub avg_wait_ms: u64,
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use super::*;
#[test]
fn test_priority_ordering() {
assert!(Priority::Critical > Priority::High);
assert!(Priority::High > Priority::Normal);
assert!(Priority::Normal > Priority::Low);
assert!(Priority::Low > Priority::Background);
}
#[test]
fn test_priority_queue_basic() {
let queue: PriorityQueue<&str> = PriorityQueue::new(QueueConfig::default());
queue.push_with_priority("low", Priority::Low).unwrap();
queue.push_with_priority("high", Priority::High).unwrap();
queue
.push_with_priority("normal", Priority::Normal)
.unwrap();
assert_eq!(queue.pop().unwrap().data, "high");
assert_eq!(queue.pop().unwrap().data, "normal");
assert_eq!(queue.pop().unwrap().data, "low");
}
#[test]
fn test_priority_queue_fifo_within_priority() {
let queue: PriorityQueue<u32> = PriorityQueue::new(QueueConfig::default());
queue.push_with_priority(1, Priority::Normal).unwrap();
queue.push_with_priority(2, Priority::Normal).unwrap();
queue.push_with_priority(3, Priority::Normal).unwrap();
assert_eq!(queue.pop().unwrap().data, 1);
assert_eq!(queue.pop().unwrap().data, 2);
assert_eq!(queue.pop().unwrap().data, 3);
}
#[test]
fn test_queue_full() {
let config = QueueConfig::new().with_max_size(2);
let queue: PriorityQueue<u32> = PriorityQueue::new(config);
queue.push(1).unwrap();
queue.push(2).unwrap();
assert_eq!(queue.push(3), Err(QueueError::QueueFull));
}
#[test]
fn test_queue_stats() {
let queue: PriorityQueue<u32> = PriorityQueue::new(QueueConfig::default());
queue.push(1).unwrap();
queue.push(2).unwrap();
let _ = queue.pop();
let stats = queue.stats();
assert_eq!(stats.enqueued, 2);
assert_eq!(stats.dequeued, 1);
assert_eq!(stats.current_size, 1);
}
#[test]
fn test_effective_priority_boost() {
let request = QueuedRequest {
data: "test",
priority: Priority::Low,
queued_at: Instant::now() - Duration::from_secs(120), sequence: 0,
};
assert!(request.effective_priority() > Priority::Low);
}
}