use chrono::{DateTime, Utc};
use peat_protocol::cot::CotEvent;
use std::collections::VecDeque;
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::time::Instant;
use crate::config::QueueConfig;
use crate::error::TakError;
use crate::metrics::QueueDepthMetrics;
use crate::traits::Priority;
#[derive(Debug, Clone)]
pub struct QueuedMessage {
pub event: CotEvent,
pub priority: Priority,
pub enqueued_at: Instant,
pub stale_time: DateTime<Utc>,
pub size_bytes: usize,
}
pub struct TakMessageQueue {
config: QueueConfig,
queues: [VecDeque<QueuedMessage>; 5],
total_bytes: AtomicUsize,
stale_dropped: AtomicU64,
full_dropped: AtomicU64,
}
impl TakMessageQueue {
pub fn new(config: QueueConfig) -> Self {
Self {
config,
queues: Default::default(),
total_bytes: AtomicUsize::new(0),
stale_dropped: AtomicU64::new(0),
full_dropped: AtomicU64::new(0),
}
}
pub fn enqueue(&mut self, event: CotEvent, priority: Priority) -> Result<(), TakError> {
let size_bytes = event.to_xml().map(|xml| xml.len()).unwrap_or(500);
let stale_time = event.stale;
let msg = QueuedMessage {
event,
priority,
enqueued_at: Instant::now(),
stale_time,
size_bytes,
};
let queue_idx = Self::priority_to_index(priority);
let limit = self.config.priority_limits.limit_for(priority);
if self.queues[queue_idx].len() >= limit {
if !self.drop_lowest_priority() {
self.full_dropped.fetch_add(1, Ordering::Relaxed);
return Err(TakError::QueueFull);
}
}
if self.total_bytes.load(Ordering::Relaxed) + size_bytes > self.config.max_bytes
&& !self.drop_lowest_priority()
{
self.full_dropped.fetch_add(1, Ordering::Relaxed);
return Err(TakError::QueueFull);
}
self.queues[queue_idx].push_back(msg);
self.total_bytes.fetch_add(size_bytes, Ordering::Relaxed);
Ok(())
}
pub fn dequeue(&mut self) -> Option<QueuedMessage> {
let now = Utc::now();
for queue in &mut self.queues {
while let Some(msg) = queue.pop_front() {
self.total_bytes
.fetch_sub(msg.size_bytes, Ordering::Relaxed);
if self.config.filter_stale && msg.stale_time < now {
self.stale_dropped.fetch_add(1, Ordering::Relaxed);
continue;
}
return Some(msg);
}
}
None
}
pub fn peek(&self) -> Option<&QueuedMessage> {
for queue in &self.queues {
if let Some(msg) = queue.front() {
return Some(msg);
}
}
None
}
pub fn metrics(&self) -> QueueDepthMetrics {
QueueDepthMetrics {
p1_depth: self.queues[0].len(),
p2_depth: self.queues[1].len(),
p3_depth: self.queues[2].len(),
p4_depth: self.queues[3].len(),
p5_depth: self.queues[4].len(),
total_bytes: self.total_bytes.load(Ordering::Relaxed),
stale_dropped: self.stale_dropped.load(Ordering::Relaxed),
}
}
pub fn is_empty(&self) -> bool {
self.queues.iter().all(|q| q.is_empty())
}
pub fn len(&self) -> usize {
self.queues.iter().map(|q| q.len()).sum()
}
pub fn clear(&mut self) {
for queue in &mut self.queues {
queue.clear();
}
self.total_bytes.store(0, Ordering::Relaxed);
}
fn drop_lowest_priority(&mut self) -> bool {
for queue_idx in (1..5).rev() {
if let Some(msg) = self.queues[queue_idx].pop_front() {
self.total_bytes
.fetch_sub(msg.size_bytes, Ordering::Relaxed);
self.full_dropped.fetch_add(1, Ordering::Relaxed);
return true;
}
}
false
}
fn priority_to_index(priority: Priority) -> usize {
match priority {
1 => 0,
2 => 1,
3 => 2,
4 => 3,
_ => 4, }
}
}
#[cfg(test)]
mod tests {
use super::*;
use peat_protocol::cot::{CotEventBuilder, CotPoint, CotType};
fn make_event(uid: &str) -> CotEvent {
CotEventBuilder::new()
.uid(uid)
.cot_type(CotType::new("a-f-G"))
.point(CotPoint::new(34.0, -118.0))
.build()
.expect("failed to build test event")
}
#[test]
fn test_enqueue_dequeue_priority_order() {
let mut queue = TakMessageQueue::new(QueueConfig::default());
queue.enqueue(make_event("p5"), 5).unwrap();
queue.enqueue(make_event("p3"), 3).unwrap();
queue.enqueue(make_event("p1"), 1).unwrap();
queue.enqueue(make_event("p2"), 2).unwrap();
queue.enqueue(make_event("p4"), 4).unwrap();
assert_eq!(queue.dequeue().unwrap().event.uid, "p1");
assert_eq!(queue.dequeue().unwrap().event.uid, "p2");
assert_eq!(queue.dequeue().unwrap().event.uid, "p3");
assert_eq!(queue.dequeue().unwrap().event.uid, "p4");
assert_eq!(queue.dequeue().unwrap().event.uid, "p5");
assert!(queue.dequeue().is_none());
}
#[test]
fn test_queue_metrics() {
let mut queue = TakMessageQueue::new(QueueConfig::default());
queue.enqueue(make_event("1"), 1).unwrap();
queue.enqueue(make_event("2"), 1).unwrap();
queue.enqueue(make_event("3"), 3).unwrap();
let metrics = queue.metrics();
assert_eq!(metrics.p1_depth, 2);
assert_eq!(metrics.p3_depth, 1);
assert_eq!(metrics.total_messages(), 3);
}
#[test]
fn test_drop_lowest_priority() {
let config = QueueConfig {
max_messages: 100,
max_bytes: 100, ..Default::default()
};
let mut queue = TakMessageQueue::new(config);
for i in 0..5 {
let _ = queue.enqueue(make_event(&format!("p5-{}", i)), 5);
}
let result = queue.enqueue(make_event("p1"), 1);
assert!(result.is_ok() || matches!(result, Err(TakError::QueueFull)));
}
#[test]
fn test_queue_is_empty() {
let mut queue = TakMessageQueue::new(QueueConfig::default());
assert!(queue.is_empty());
queue.enqueue(make_event("1"), 1).unwrap();
assert!(!queue.is_empty());
queue.dequeue();
assert!(queue.is_empty());
}
}