Skip to main content

kvbm_engine/offload/
queue.rs

1// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Cancellable queue implementation using crossbeam SegQueue.
5//!
6//! Provides a lock-free queue wrapper that supports active cancellation via
7//! a sweeper task that can iterate through queued items and remove those
8//! belonging to cancelled transfers.
9
10use std::sync::atomic::{AtomicUsize, Ordering};
11
12use crossbeam_queue::SegQueue;
13use dashmap::DashSet;
14use tokio::sync::Notify;
15
16use super::handle::TransferId;
17
18/// A queued item with its associated transfer ID.
19pub struct QueueItem<T> {
20    /// The transfer this item belongs to
21    pub transfer_id: TransferId,
22    /// The actual data
23    pub data: T,
24}
25
26impl<T> QueueItem<T> {
27    /// Create a new queue item.
28    pub fn new(transfer_id: TransferId, data: T) -> Self {
29        Self { transfer_id, data }
30    }
31}
32
33/// A lock-free queue that supports active cancellation via sweeping.
34///
35/// Unlike mpsc channels where cancellation can only be checked at dequeue time,
36/// this queue allows a dedicated sweeper task to iterate through queued items
37/// and remove those belonging to cancelled transfers. This ensures that
38/// `ImmutableBlock` guards are dropped promptly when a transfer is cancelled.
39///
40/// # Architecture
41///
42/// ```text
43/// Producer ──► [SegQueue] ◄── Consumer
44///                  ▲
45///                  │
46///             [Sweeper Task]
47///                  │
48///            (removes cancelled items)
49/// ```
50pub struct CancellableQueue<T> {
51    /// The underlying lock-free queue
52    inner: SegQueue<QueueItem<T>>,
53    /// Set of cancelled transfer IDs
54    cancelled: DashSet<TransferId>,
55    /// Approximate length for monitoring (not exact due to concurrent access)
56    len: AtomicUsize,
57    /// Wakes consumers without waiting for a wall-clock poll interval.
58    notify: Notify,
59}
60
61impl<T> CancellableQueue<T> {
62    /// Create a new cancellable queue.
63    pub fn new() -> Self {
64        Self {
65            inner: SegQueue::new(),
66            cancelled: DashSet::new(),
67            len: AtomicUsize::new(0),
68            notify: Notify::new(),
69        }
70    }
71
72    /// Push an item onto the queue.
73    ///
74    /// If the transfer has already been cancelled, the item is dropped immediately.
75    /// Returns `true` if the item was queued, `false` if it was dropped due to cancellation.
76    pub fn push(&self, transfer_id: TransferId, data: T) -> bool {
77        // Fast path: check if already cancelled before queuing
78        if self.cancelled.contains(&transfer_id) {
79            return false;
80        }
81
82        self.inner.push(QueueItem::new(transfer_id, data));
83        self.len.fetch_add(1, Ordering::Relaxed);
84        self.notify.notify_one();
85        true
86    }
87
88    /// Wait until a producer pushes new work or cancellation changes.
89    pub async fn notified(&self) {
90        self.notify.notified().await;
91    }
92
93    /// Pop an item from the queue.
94    ///
95    /// Returns `None` if the queue is empty.
96    /// Items from cancelled transfers may still be returned - use `pop_valid()`
97    /// if you want to skip cancelled items automatically.
98    pub fn pop(&self) -> Option<QueueItem<T>> {
99        let item = self.inner.pop();
100        if item.is_some() {
101            self.len.fetch_sub(1, Ordering::Relaxed);
102        }
103        item
104    }
105
106    /// Pop a valid (non-cancelled) item from the queue.
107    ///
108    /// Skips and drops items belonging to cancelled transfers.
109    /// Returns `None` if no valid items are available.
110    pub fn pop_valid(&self) -> Option<QueueItem<T>> {
111        loop {
112            match self.inner.pop() {
113                Some(item) => {
114                    self.len.fetch_sub(1, Ordering::Relaxed);
115                    if self.cancelled.contains(&item.transfer_id) {
116                        // Drop cancelled item and try again
117                        continue;
118                    }
119                    return Some(item);
120                }
121                None => return None,
122            }
123        }
124    }
125
126    /// Mark a transfer as cancelled.
127    ///
128    /// Items belonging to this transfer will be:
129    /// - Dropped immediately if pushed after this call
130    /// - Removed by the sweeper task if already in the queue
131    /// - Skipped by `pop_valid()` if dequeued
132    pub fn mark_cancelled(&self, transfer_id: TransferId) {
133        self.cancelled.insert(transfer_id);
134        self.notify.notify_waiters();
135    }
136
137    /// Check if a transfer has been cancelled.
138    pub fn is_cancelled(&self, transfer_id: TransferId) -> bool {
139        self.cancelled.contains(&transfer_id)
140    }
141
142    /// Remove cancelled items from the queue.
143    ///
144    /// This is called by the sweeper task to actively remove items from
145    /// cancelled transfers, ensuring their resources (like `ImmutableBlock` guards)
146    /// are released promptly.
147    ///
148    /// Returns the number of items removed.
149    ///
150    /// # Implementation Note
151    ///
152    /// This performs a full drain-and-requeue operation. While not ideal for
153    /// very large queues, it ensures correctness with the lock-free SegQueue.
154    /// For typical offload workloads (batches of 64-256 blocks), this is efficient.
155    pub fn sweep(&self) -> usize {
156        if self.cancelled.is_empty() {
157            return 0;
158        }
159
160        // Drain all items and requeue non-cancelled ones
161        let mut removed = 0;
162        let mut kept = Vec::new();
163
164        while let Some(item) = self.inner.pop() {
165            if self.cancelled.contains(&item.transfer_id) {
166                removed += 1;
167                // Item is dropped here, releasing any held resources
168            } else {
169                kept.push(item);
170            }
171        }
172
173        // Requeue kept items
174        for item in kept {
175            self.inner.push(item);
176        }
177
178        // Update length counter
179        if removed > 0 {
180            self.len.fetch_sub(removed, Ordering::Relaxed);
181        }
182
183        removed
184    }
185
186    /// Clear the cancelled set for a specific transfer.
187    ///
188    /// Called when a transfer is fully complete to clean up the cancelled set.
189    pub fn clear_cancelled(&self, transfer_id: TransferId) {
190        self.cancelled.remove(&transfer_id);
191    }
192
193    /// Get the approximate queue length.
194    ///
195    /// This is not exact due to concurrent modifications but useful for monitoring.
196    pub fn len_approx(&self) -> usize {
197        self.len.load(Ordering::Relaxed)
198    }
199
200    /// Check if the queue is approximately empty.
201    pub fn is_empty_approx(&self) -> bool {
202        self.len_approx() == 0
203    }
204
205    /// Get the number of cancelled transfers being tracked.
206    pub fn cancelled_count(&self) -> usize {
207        self.cancelled.len()
208    }
209}
210
211impl<T> Default for CancellableQueue<T> {
212    fn default() -> Self {
213        Self::new()
214    }
215}
216
217#[cfg(test)]
218mod tests {
219    use super::*;
220
221    #[test]
222    fn test_basic_push_pop() {
223        let queue: CancellableQueue<i32> = CancellableQueue::new();
224        let id = TransferId::new();
225
226        assert!(queue.push(id, 42));
227        assert_eq!(queue.len_approx(), 1);
228
229        let item = queue.pop().unwrap();
230        assert_eq!(item.transfer_id, id);
231        assert_eq!(item.data, 42);
232        assert_eq!(queue.len_approx(), 0);
233    }
234
235    #[test]
236    fn test_cancelled_push_rejected() {
237        let queue: CancellableQueue<i32> = CancellableQueue::new();
238        let id = TransferId::new();
239
240        queue.mark_cancelled(id);
241        assert!(!queue.push(id, 42));
242        assert_eq!(queue.len_approx(), 0);
243    }
244
245    #[test]
246    fn test_pop_valid_skips_cancelled() {
247        let queue: CancellableQueue<i32> = CancellableQueue::new();
248        let id1 = TransferId::new();
249        let id2 = TransferId::new();
250
251        queue.push(id1, 1);
252        queue.push(id2, 2);
253        queue.push(id1, 3);
254
255        queue.mark_cancelled(id1);
256
257        // pop_valid should skip items from id1
258        let item = queue.pop_valid().unwrap();
259        assert_eq!(item.transfer_id, id2);
260        assert_eq!(item.data, 2);
261
262        // No more valid items
263        assert!(queue.pop_valid().is_none());
264    }
265
266    #[test]
267    fn test_sweep_removes_cancelled() {
268        let queue: CancellableQueue<i32> = CancellableQueue::new();
269        let id1 = TransferId::new();
270        let id2 = TransferId::new();
271
272        queue.push(id1, 1);
273        queue.push(id2, 2);
274        queue.push(id1, 3);
275        queue.push(id2, 4);
276
277        assert_eq!(queue.len_approx(), 4);
278
279        queue.mark_cancelled(id1);
280        let removed = queue.sweep();
281
282        assert_eq!(removed, 2);
283        assert_eq!(queue.len_approx(), 2);
284
285        // Remaining items should be from id2
286        let item1 = queue.pop().unwrap();
287        let item2 = queue.pop().unwrap();
288        assert_eq!(item1.transfer_id, id2);
289        assert_eq!(item2.transfer_id, id2);
290    }
291
292    #[test]
293    fn test_sweep_empty_cancelled_set() {
294        let queue: CancellableQueue<i32> = CancellableQueue::new();
295        let id = TransferId::new();
296
297        queue.push(id, 1);
298        queue.push(id, 2);
299
300        // Sweep with no cancelled transfers should be a no-op
301        let removed = queue.sweep();
302        assert_eq!(removed, 0);
303        assert_eq!(queue.len_approx(), 2);
304    }
305
306    #[test]
307    fn test_clear_cancelled() {
308        let queue: CancellableQueue<i32> = CancellableQueue::new();
309        let id = TransferId::new();
310
311        queue.mark_cancelled(id);
312        assert!(queue.is_cancelled(id));
313        assert_eq!(queue.cancelled_count(), 1);
314
315        queue.clear_cancelled(id);
316        assert!(!queue.is_cancelled(id));
317        assert_eq!(queue.cancelled_count(), 0);
318    }
319
320    /// Test multiple transfer IDs with interleaved cancellation.
321    #[test]
322    fn test_multiple_transfers_interleaved() {
323        let queue: CancellableQueue<i32> = CancellableQueue::new();
324        let id1 = TransferId::new();
325        let id2 = TransferId::new();
326        let id3 = TransferId::new();
327
328        // Push items from different transfers
329        queue.push(id1, 1);
330        queue.push(id2, 2);
331        queue.push(id1, 3);
332        queue.push(id3, 4);
333        queue.push(id2, 5);
334        queue.push(id3, 6);
335
336        assert_eq!(queue.len_approx(), 6);
337
338        // Cancel id2
339        queue.mark_cancelled(id2);
340        let removed = queue.sweep();
341        assert_eq!(removed, 2); // items 2 and 5
342        assert_eq!(queue.len_approx(), 4);
343
344        // Cancel id1
345        queue.mark_cancelled(id1);
346        let removed = queue.sweep();
347        assert_eq!(removed, 2); // items 1 and 3
348        assert_eq!(queue.len_approx(), 2);
349
350        // Remaining should be from id3
351        let item1 = queue.pop().unwrap();
352        let item2 = queue.pop().unwrap();
353        assert_eq!(item1.transfer_id, id3);
354        assert_eq!(item2.transfer_id, id3);
355    }
356
357    /// Test sweep with empty queue.
358    #[test]
359    fn test_sweep_empty_queue() {
360        let queue: CancellableQueue<i32> = CancellableQueue::new();
361        let id = TransferId::new();
362
363        queue.mark_cancelled(id);
364        let removed = queue.sweep();
365        assert_eq!(removed, 0);
366        assert!(queue.is_empty_approx());
367    }
368
369    /// Test pop_valid exhausts queue of only cancelled items.
370    #[test]
371    fn test_pop_valid_exhausts_cancelled() {
372        let queue: CancellableQueue<i32> = CancellableQueue::new();
373        let id = TransferId::new();
374
375        queue.push(id, 1);
376        queue.push(id, 2);
377        queue.push(id, 3);
378
379        queue.mark_cancelled(id);
380
381        // pop_valid should return None after exhausting cancelled items
382        assert!(queue.pop_valid().is_none());
383        // Queue should be empty now (items were dropped during pop_valid)
384        assert_eq!(queue.len_approx(), 0);
385    }
386
387    /// Test that cancelled items are dropped (not leaked) during sweep.
388    #[test]
389    fn test_sweep_drops_items() {
390        use std::sync::Arc;
391        use std::sync::atomic::{AtomicUsize, Ordering};
392
393        struct DropCounter {
394            counter: Arc<AtomicUsize>,
395        }
396
397        impl Drop for DropCounter {
398            fn drop(&mut self) {
399                self.counter.fetch_add(1, Ordering::SeqCst);
400            }
401        }
402
403        let drop_count = Arc::new(AtomicUsize::new(0));
404        let queue: CancellableQueue<DropCounter> = CancellableQueue::new();
405        let id = TransferId::new();
406
407        queue.push(
408            id,
409            DropCounter {
410                counter: drop_count.clone(),
411            },
412        );
413        queue.push(
414            id,
415            DropCounter {
416                counter: drop_count.clone(),
417            },
418        );
419        queue.push(
420            id,
421            DropCounter {
422                counter: drop_count.clone(),
423            },
424        );
425
426        assert_eq!(drop_count.load(Ordering::SeqCst), 0);
427
428        queue.mark_cancelled(id);
429        let removed = queue.sweep();
430
431        assert_eq!(removed, 3);
432        assert_eq!(drop_count.load(Ordering::SeqCst), 3);
433    }
434}