use std::sync::atomic::{AtomicUsize, Ordering};
use crossbeam_queue::SegQueue;
use dashmap::DashSet;
use tokio::sync::Notify;
use super::handle::TransferId;
pub struct QueueItem<T> {
pub transfer_id: TransferId,
pub data: T,
}
impl<T> QueueItem<T> {
pub fn new(transfer_id: TransferId, data: T) -> Self {
Self { transfer_id, data }
}
}
pub struct CancellableQueue<T> {
inner: SegQueue<QueueItem<T>>,
cancelled: DashSet<TransferId>,
len: AtomicUsize,
notify: Notify,
}
impl<T> CancellableQueue<T> {
pub fn new() -> Self {
Self {
inner: SegQueue::new(),
cancelled: DashSet::new(),
len: AtomicUsize::new(0),
notify: Notify::new(),
}
}
pub fn push(&self, transfer_id: TransferId, data: T) -> bool {
if self.cancelled.contains(&transfer_id) {
return false;
}
self.inner.push(QueueItem::new(transfer_id, data));
self.len.fetch_add(1, Ordering::Relaxed);
self.notify.notify_one();
true
}
pub async fn notified(&self) {
self.notify.notified().await;
}
pub fn pop(&self) -> Option<QueueItem<T>> {
let item = self.inner.pop();
if item.is_some() {
self.len.fetch_sub(1, Ordering::Relaxed);
}
item
}
pub fn pop_valid(&self) -> Option<QueueItem<T>> {
loop {
match self.inner.pop() {
Some(item) => {
self.len.fetch_sub(1, Ordering::Relaxed);
if self.cancelled.contains(&item.transfer_id) {
continue;
}
return Some(item);
}
None => return None,
}
}
}
pub fn mark_cancelled(&self, transfer_id: TransferId) {
self.cancelled.insert(transfer_id);
self.notify.notify_waiters();
}
pub fn is_cancelled(&self, transfer_id: TransferId) -> bool {
self.cancelled.contains(&transfer_id)
}
pub fn sweep(&self) -> usize {
if self.cancelled.is_empty() {
return 0;
}
let mut removed = 0;
let mut kept = Vec::new();
while let Some(item) = self.inner.pop() {
if self.cancelled.contains(&item.transfer_id) {
removed += 1;
} else {
kept.push(item);
}
}
for item in kept {
self.inner.push(item);
}
if removed > 0 {
self.len.fetch_sub(removed, Ordering::Relaxed);
}
removed
}
pub fn clear_cancelled(&self, transfer_id: TransferId) {
self.cancelled.remove(&transfer_id);
}
pub fn len_approx(&self) -> usize {
self.len.load(Ordering::Relaxed)
}
pub fn is_empty_approx(&self) -> bool {
self.len_approx() == 0
}
pub fn cancelled_count(&self) -> usize {
self.cancelled.len()
}
}
impl<T> Default for CancellableQueue<T> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_push_pop() {
let queue: CancellableQueue<i32> = CancellableQueue::new();
let id = TransferId::new();
assert!(queue.push(id, 42));
assert_eq!(queue.len_approx(), 1);
let item = queue.pop().unwrap();
assert_eq!(item.transfer_id, id);
assert_eq!(item.data, 42);
assert_eq!(queue.len_approx(), 0);
}
#[test]
fn test_cancelled_push_rejected() {
let queue: CancellableQueue<i32> = CancellableQueue::new();
let id = TransferId::new();
queue.mark_cancelled(id);
assert!(!queue.push(id, 42));
assert_eq!(queue.len_approx(), 0);
}
#[test]
fn test_pop_valid_skips_cancelled() {
let queue: CancellableQueue<i32> = CancellableQueue::new();
let id1 = TransferId::new();
let id2 = TransferId::new();
queue.push(id1, 1);
queue.push(id2, 2);
queue.push(id1, 3);
queue.mark_cancelled(id1);
let item = queue.pop_valid().unwrap();
assert_eq!(item.transfer_id, id2);
assert_eq!(item.data, 2);
assert!(queue.pop_valid().is_none());
}
#[test]
fn test_sweep_removes_cancelled() {
let queue: CancellableQueue<i32> = CancellableQueue::new();
let id1 = TransferId::new();
let id2 = TransferId::new();
queue.push(id1, 1);
queue.push(id2, 2);
queue.push(id1, 3);
queue.push(id2, 4);
assert_eq!(queue.len_approx(), 4);
queue.mark_cancelled(id1);
let removed = queue.sweep();
assert_eq!(removed, 2);
assert_eq!(queue.len_approx(), 2);
let item1 = queue.pop().unwrap();
let item2 = queue.pop().unwrap();
assert_eq!(item1.transfer_id, id2);
assert_eq!(item2.transfer_id, id2);
}
#[test]
fn test_sweep_empty_cancelled_set() {
let queue: CancellableQueue<i32> = CancellableQueue::new();
let id = TransferId::new();
queue.push(id, 1);
queue.push(id, 2);
let removed = queue.sweep();
assert_eq!(removed, 0);
assert_eq!(queue.len_approx(), 2);
}
#[test]
fn test_clear_cancelled() {
let queue: CancellableQueue<i32> = CancellableQueue::new();
let id = TransferId::new();
queue.mark_cancelled(id);
assert!(queue.is_cancelled(id));
assert_eq!(queue.cancelled_count(), 1);
queue.clear_cancelled(id);
assert!(!queue.is_cancelled(id));
assert_eq!(queue.cancelled_count(), 0);
}
#[test]
fn test_multiple_transfers_interleaved() {
let queue: CancellableQueue<i32> = CancellableQueue::new();
let id1 = TransferId::new();
let id2 = TransferId::new();
let id3 = TransferId::new();
queue.push(id1, 1);
queue.push(id2, 2);
queue.push(id1, 3);
queue.push(id3, 4);
queue.push(id2, 5);
queue.push(id3, 6);
assert_eq!(queue.len_approx(), 6);
queue.mark_cancelled(id2);
let removed = queue.sweep();
assert_eq!(removed, 2); assert_eq!(queue.len_approx(), 4);
queue.mark_cancelled(id1);
let removed = queue.sweep();
assert_eq!(removed, 2); assert_eq!(queue.len_approx(), 2);
let item1 = queue.pop().unwrap();
let item2 = queue.pop().unwrap();
assert_eq!(item1.transfer_id, id3);
assert_eq!(item2.transfer_id, id3);
}
#[test]
fn test_sweep_empty_queue() {
let queue: CancellableQueue<i32> = CancellableQueue::new();
let id = TransferId::new();
queue.mark_cancelled(id);
let removed = queue.sweep();
assert_eq!(removed, 0);
assert!(queue.is_empty_approx());
}
#[test]
fn test_pop_valid_exhausts_cancelled() {
let queue: CancellableQueue<i32> = CancellableQueue::new();
let id = TransferId::new();
queue.push(id, 1);
queue.push(id, 2);
queue.push(id, 3);
queue.mark_cancelled(id);
assert!(queue.pop_valid().is_none());
assert_eq!(queue.len_approx(), 0);
}
#[test]
fn test_sweep_drops_items() {
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
struct DropCounter {
counter: Arc<AtomicUsize>,
}
impl Drop for DropCounter {
fn drop(&mut self) {
self.counter.fetch_add(1, Ordering::SeqCst);
}
}
let drop_count = Arc::new(AtomicUsize::new(0));
let queue: CancellableQueue<DropCounter> = CancellableQueue::new();
let id = TransferId::new();
queue.push(
id,
DropCounter {
counter: drop_count.clone(),
},
);
queue.push(
id,
DropCounter {
counter: drop_count.clone(),
},
);
queue.push(
id,
DropCounter {
counter: drop_count.clone(),
},
);
assert_eq!(drop_count.load(Ordering::SeqCst), 0);
queue.mark_cancelled(id);
let removed = queue.sweep();
assert_eq!(removed, 3);
assert_eq!(drop_count.load(Ordering::SeqCst), 3);
}
}