moirai_executor/schedule/queue/
mod.rs1use std::sync::{
4 atomic::{AtomicUsize, Ordering},
5 Arc,
6};
7
8use moirai_core::Priority;
9use moirai_scheduler::{ChaseLevDeque, ChaseLevStealer, StealResult};
10use moirai_utils::CacheAligned;
11
12use super::job::ScheduledJob;
13
14const PRIORITY_LEVELS: usize = Priority::Critical.index() + 1;
16pub(super) const INJECTOR_CAPACITY: usize = 1024;
28const PRIORITY_POP_ORDER: [usize; PRIORITY_LEVELS] = [
30 Priority::Critical.index(),
31 Priority::High.index(),
32 Priority::Normal.index(),
33 Priority::Low.index(),
34];
35
36pub(crate) struct WorkerQueues<const CAPACITY: usize> {
46 local_stealers: [ChaseLevStealer<ScheduledJob>; PRIORITY_LEVELS],
47 injector: moirai_utils::queue::LockFreeQueue<(Priority, ScheduledJob)>,
48 len: CacheAligned<AtomicUsize>,
53}
54
55pub(crate) struct WorkerQueueOwner<const CAPACITY: usize> {
57 local_queues: [ChaseLevDeque<ScheduledJob>; PRIORITY_LEVELS],
58 shared: Arc<WorkerQueues<CAPACITY>>,
59}
60
61impl<const CAPACITY: usize> WorkerQueues<CAPACITY> {
62 pub(crate) fn new() -> (WorkerQueueOwner<CAPACITY>, Arc<Self>) {
64 let local_queues = std::array::from_fn(|_| ChaseLevDeque::new(CAPACITY));
65 let local_stealers = std::array::from_fn(|index| local_queues[index].stealer());
66 let shared = Arc::new(Self {
67 local_stealers,
68 injector: moirai_utils::queue::LockFreeQueue::with_capacity(INJECTOR_CAPACITY),
69 len: CacheAligned::new(AtomicUsize::new(0)),
70 });
71 (
72 WorkerQueueOwner {
73 local_queues,
74 shared: Arc::clone(&shared),
75 },
76 shared,
77 )
78 }
79
80 pub(crate) fn try_push_external(
82 &self,
83 priority: Priority,
84 job: ScheduledJob,
85 ) -> Option<ScheduledJob> {
86 match self.injector.try_enqueue((priority, job)) {
87 Ok(()) => {
88 self.len.fetch_add(1, Ordering::Relaxed);
89 None
90 }
91 Err((_priority, job)) => Some(job),
92 }
93 }
94
95 pub(crate) fn steal_one(&self) -> Option<ScheduledJob> {
97 if self.len.load(Ordering::Relaxed) == 0 {
98 return None;
99 }
100 for &index in &PRIORITY_POP_ORDER {
101 loop {
102 match self.local_stealers[index].steal() {
103 StealResult::Success(job) => {
104 self.len.fetch_sub(1, Ordering::Relaxed);
105 return Some(job);
106 }
107 StealResult::Retry => continue,
108 StealResult::Empty => break,
109 }
110 }
111 }
112 if let Some((_priority, job)) = self.injector.try_dequeue() {
113 self.len.fetch_sub(1, Ordering::Relaxed);
114 return Some(job);
115 }
116 None
117 }
118
119 pub(crate) fn is_empty(&self) -> bool {
121 self.len.load(Ordering::Relaxed) == 0
122 }
123
124 #[cfg(test)]
126 pub(crate) fn len(&self) -> usize {
127 self.len.load(Ordering::Relaxed)
128 }
129
130 #[cfg(test)]
132 pub(crate) fn injector_capacity(&self) -> usize {
133 self.injector.capacity()
134 }
135}
136
137impl<const CAPACITY: usize> WorkerQueueOwner<CAPACITY> {
138 pub(crate) fn pop_local(&mut self) -> Option<ScheduledJob> {
139 if self.shared.len.load(Ordering::Relaxed) == 0 {
140 return None;
141 }
142 for &index in &PRIORITY_POP_ORDER {
143 if let Some(job) = self.local_queues[index].pop() {
144 self.shared.len.fetch_sub(1, Ordering::Relaxed);
145 return Some(job);
146 }
147 }
148 while let Some((priority, job)) = self.shared.injector.try_dequeue() {
149 self.local_queues[priority.index()].push(job);
150 }
151 for &index in &PRIORITY_POP_ORDER {
152 if let Some(job) = self.local_queues[index].pop() {
153 self.shared.len.fetch_sub(1, Ordering::Relaxed);
154 return Some(job);
155 }
156 }
157 None
158 }
159
160 pub(crate) fn steal_batch(&mut self, target: &WorkerQueues<CAPACITY>) -> Option<ScheduledJob> {
162 if target.len.load(Ordering::Relaxed) == 0 {
163 return None;
164 }
165
166 for &index in &PRIORITY_POP_ORDER {
168 loop {
169 match target.local_stealers[index].steal_batch() {
170 StealResult::Success(mut batch) => {
171 let first_job = batch
172 .next()
173 .expect("invariant: successful batch contains one job");
174 let mut pushed_count = 0;
175 for job in batch {
176 self.local_queues[index].push(job);
177 pushed_count += 1;
178 }
179 if pushed_count > 0 {
180 self.shared.len.fetch_add(pushed_count, Ordering::Relaxed);
181 }
182 target.len.fetch_sub(pushed_count + 1, Ordering::Relaxed);
183 return Some(first_job);
184 }
185 StealResult::Retry => continue,
186 StealResult::Empty => break,
187 }
188 }
189 }
190
191 if let Some((_priority, first_job)) = target.injector.try_dequeue() {
193 let mut pushed_count = 0;
194 while pushed_count < 15 {
196 if let Some((p, job)) = target.injector.try_dequeue() {
197 self.local_queues[p.index()].push(job);
198 pushed_count += 1;
199 } else {
200 break;
201 }
202 }
203 if pushed_count > 0 {
204 self.shared.len.fetch_add(pushed_count, Ordering::Relaxed);
205 }
206 target.len.fetch_sub(pushed_count + 1, Ordering::Relaxed);
207 return Some(first_job);
208 }
209
210 None
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use std::sync::{Arc, Mutex};
217
218 use super::WorkerQueues;
219 use crate::schedule::job::ScheduledJob;
220 use moirai_core::Priority;
221
222 #[test]
223 fn worker_queue_pops_highest_priority_first() {
224 let observed = Arc::new(Mutex::new(Vec::new()));
225 let (mut owner, queues) = WorkerQueues::<256>::new();
226
227 for (priority, value) in [(Priority::Low, 1), (Priority::Critical, 2)] {
228 let observed = Arc::clone(&observed);
229 let () = queues
230 .try_push_external(
231 priority,
232 ScheduledJob::new(move |_| {
233 observed.lock().unwrap().push(value);
234 }),
235 )
236 .map_or((), |_| panic!("test queue has capacity"));
237 }
238
239 owner.pop_local().unwrap().execute(0);
240 owner.pop_local().unwrap().execute(0);
241
242 assert_eq!(*observed.lock().unwrap(), vec![2, 1]);
243 assert_eq!(queues.len(), 0);
244 }
245
246 #[test]
247 fn injector_uses_sane_default_capacity_not_65536() {
248 let (_owner, queues) = WorkerQueues::<256>::new();
252 assert_eq!(queues.injector_capacity(), super::INJECTOR_CAPACITY);
253 assert_eq!(queues.injector_capacity(), 1024);
254 assert_ne!(queues.injector_capacity(), 65536);
255 }
256
257 #[test]
258 fn injector_round_trips_through_external_push() {
259 let observed = Arc::new(Mutex::new(Vec::new()));
262 let (mut owner, queues) = WorkerQueues::<256>::new();
263
264 for (priority, value) in [(Priority::Normal, 7), (Priority::Critical, 9)] {
265 let observed = Arc::clone(&observed);
266 let () = queues
267 .try_push_external(
268 priority,
269 ScheduledJob::new(move |_| {
270 observed.lock().unwrap().push(value);
271 }),
272 )
273 .map_or((), |_| panic!("test queue has capacity"));
274 }
275
276 owner.pop_local().unwrap().execute(0);
278 owner.pop_local().unwrap().execute(0);
279
280 assert_eq!(*observed.lock().unwrap(), vec![9, 7]);
281 assert_eq!(queues.len(), 0);
282 }
283
284 #[test]
285 fn full_injector_returns_and_drops_rejected_job_once() {
286 let (_owner, queues) = WorkerQueues::<256>::new();
287 for _ in 0..super::INJECTOR_CAPACITY {
288 let () = queues
289 .try_push_external(Priority::Normal, ScheduledJob::new(|_| {}))
290 .map_or((), |_| panic!("capacity-sized admission must succeed"));
291 }
292
293 let capture = Arc::new(());
294 let rejected_capture = Arc::clone(&capture);
295 let rejected = queues
296 .try_push_external(
297 Priority::Normal,
298 ScheduledJob::new(move |_| drop(rejected_capture)),
299 )
300 .expect("one job beyond capacity must be rejected");
301
302 assert_eq!(queues.len(), super::INJECTOR_CAPACITY);
303 assert_eq!(Arc::strong_count(&capture), 2);
304 drop(rejected);
305 assert_eq!(Arc::strong_count(&capture), 1);
306 }
307}