Skip to main content

a3s_lane/
priority_queue.rs

1//! Stable in-process priority queue for host-owned work items.
2//!
3//! [`QueueManager`](crate::QueueManager) owns and executes async commands. Some
4//! hosts instead need to keep typed payloads locally and decide when execution
5//! starts, while still sharing Lane's priority and FIFO semantics. This module
6//! provides that smaller scheduling primitive without forcing payloads through
7//! JSON or an async command adapter.
8
9use crate::Priority;
10use std::cmp::Ordering;
11use std::collections::BinaryHeap;
12
13/// One queued value together with its stable scheduling metadata.
14#[derive(Debug)]
15pub struct PriorityItem<T> {
16    priority: Priority,
17    sequence: u64,
18    value: T,
19}
20
21impl<T> PriorityItem<T> {
22    /// Lower values are scheduled first.
23    pub fn priority(&self) -> Priority {
24        self.priority
25    }
26
27    /// Monotonic insertion order used for FIFO scheduling within a priority.
28    pub fn sequence(&self) -> u64 {
29        self.sequence
30    }
31
32    /// Borrow the queued value.
33    pub fn value(&self) -> &T {
34        &self.value
35    }
36
37    /// Mutably borrow the queued value.
38    pub fn value_mut(&mut self) -> &mut T {
39        &mut self.value
40    }
41
42    /// Consume the scheduling metadata and return the queued value.
43    pub fn into_value(self) -> T {
44        self.value
45    }
46}
47
48impl<T> PartialEq for PriorityItem<T> {
49    fn eq(&self, other: &Self) -> bool {
50        self.priority == other.priority && self.sequence == other.sequence
51    }
52}
53
54impl<T> Eq for PriorityItem<T> {}
55
56impl<T> Ord for PriorityItem<T> {
57    fn cmp(&self, other: &Self) -> Ordering {
58        // BinaryHeap is a max-heap. Reverse both keys so lower priority values
59        // run first and equal-priority values remain FIFO.
60        other
61            .priority
62            .cmp(&self.priority)
63            .then_with(|| other.sequence.cmp(&self.sequence))
64    }
65}
66
67impl<T> PartialOrd for PriorityItem<T> {
68    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
69        Some(self.cmp(other))
70    }
71}
72
73/// Typed, stable priority queue for host-controlled execution.
74///
75/// The queue never executes values itself. Lower numeric priorities are
76/// scheduled first, and insertion order is preserved within each priority.
77#[derive(Debug)]
78pub struct PriorityQueue<T> {
79    entries: BinaryHeap<PriorityItem<T>>,
80    next_sequence: u64,
81}
82
83impl<T> Default for PriorityQueue<T> {
84    fn default() -> Self {
85        Self::new()
86    }
87}
88
89impl<T> PriorityQueue<T> {
90    /// Create an empty queue.
91    pub fn new() -> Self {
92        Self {
93            entries: BinaryHeap::new(),
94            next_sequence: 0,
95        }
96    }
97
98    /// Number of pending values.
99    pub fn len(&self) -> usize {
100        self.entries.len()
101    }
102
103    /// Whether the queue has no pending values.
104    pub fn is_empty(&self) -> bool {
105        self.entries.is_empty()
106    }
107
108    /// Submit a value and return its stable insertion sequence.
109    pub fn push(&mut self, priority: Priority, value: T) -> u64 {
110        self.ensure_sequence_capacity();
111        self.next_sequence += 1;
112        let sequence = self.next_sequence;
113        self.entries.push(PriorityItem {
114            priority,
115            sequence,
116            value,
117        });
118        sequence
119    }
120
121    /// Claim the highest-priority pending value.
122    pub fn pop(&mut self) -> Option<PriorityItem<T>> {
123        self.entries.pop()
124    }
125
126    /// Return a previously claimed item without changing its original FIFO
127    /// position. This is intended for admission failures and other cases where
128    /// execution never acquired ownership of the work.
129    pub fn restore(&mut self, item: PriorityItem<T>) {
130        self.next_sequence = self.next_sequence.max(item.sequence);
131        self.entries.push(item);
132    }
133
134    /// Borrow pending items in the exact order in which they will be claimed.
135    pub fn ordered(&self) -> Vec<&PriorityItem<T>> {
136        let mut entries = self.entries.iter().collect::<Vec<_>>();
137        entries.sort_by(|left, right| {
138            left.priority
139                .cmp(&right.priority)
140                .then_with(|| left.sequence.cmp(&right.sequence))
141        });
142        entries
143    }
144
145    /// Remove all pending values.
146    pub fn clear(&mut self) {
147        self.entries.clear();
148        self.next_sequence = 0;
149    }
150
151    fn ensure_sequence_capacity(&mut self) {
152        if self.next_sequence != u64::MAX {
153            return;
154        }
155
156        // Rebase only the FIFO key. Priority remains authoritative, and
157        // ordering within every priority is preserved by the old sequence.
158        let mut entries = self.entries.drain().collect::<Vec<_>>();
159        entries.sort_by_key(|entry| entry.sequence);
160        for (index, entry) in entries.iter_mut().enumerate() {
161            entry.sequence = index as u64 + 1;
162        }
163        self.next_sequence = entries.len() as u64;
164        self.entries.extend(entries);
165    }
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171
172    #[test]
173    fn lower_priority_runs_first_and_equal_priority_is_fifo() {
174        let mut queue = PriorityQueue::new();
175        queue.push(5, "background");
176        queue.push(1, "first user");
177        queue.push(1, "second user");
178        queue.push(3, "continuation");
179
180        let drained =
181            std::iter::from_fn(|| queue.pop().map(PriorityItem::into_value)).collect::<Vec<_>>();
182
183        assert_eq!(
184            drained,
185            ["first user", "second user", "continuation", "background"]
186        );
187    }
188
189    #[test]
190    fn restore_preserves_original_fifo_position() {
191        let mut queue = PriorityQueue::new();
192        queue.push(1, "first");
193        queue.push(1, "second");
194
195        let first = queue.pop().unwrap();
196        queue.push(1, "third");
197        queue.restore(first);
198
199        let drained =
200            std::iter::from_fn(|| queue.pop().map(PriorityItem::into_value)).collect::<Vec<_>>();
201        assert_eq!(drained, ["first", "second", "third"]);
202    }
203
204    #[test]
205    fn ordered_matches_claim_order_without_mutating_the_queue() {
206        let mut queue = PriorityQueue::new();
207        queue.push(4, "later");
208        queue.push(0, "first");
209        queue.push(4, "last");
210
211        let ordered = queue
212            .ordered()
213            .into_iter()
214            .map(|entry| *entry.value())
215            .collect::<Vec<_>>();
216
217        assert_eq!(ordered, ["first", "later", "last"]);
218        assert_eq!(queue.len(), 3);
219    }
220
221    #[test]
222    fn clear_resets_pending_values_and_sequence() {
223        let mut queue = PriorityQueue::new();
224        assert_eq!(queue.push(1, "old"), 1);
225        queue.clear();
226
227        assert!(queue.is_empty());
228        assert_eq!(queue.push(1, "new"), 1);
229    }
230}