use std::sync::{
atomic::{AtomicUsize, Ordering},
Arc,
};
use moirai_core::Priority;
use moirai_scheduler::{ChaseLevDeque, ChaseLevStealer, StealResult};
use moirai_utils::CacheAligned;
use super::job::ScheduledJob;
const PRIORITY_LEVELS: usize = Priority::Critical.index() + 1;
pub(super) const INJECTOR_CAPACITY: usize = 1024;
const PRIORITY_POP_ORDER: [usize; PRIORITY_LEVELS] = [
Priority::Critical.index(),
Priority::High.index(),
Priority::Normal.index(),
Priority::Low.index(),
];
pub(crate) struct WorkerQueues<const CAPACITY: usize> {
local_stealers: [ChaseLevStealer<ScheduledJob>; PRIORITY_LEVELS],
injector: moirai_utils::queue::LockFreeQueue<(Priority, ScheduledJob)>,
len: CacheAligned<AtomicUsize>,
}
pub(crate) struct WorkerQueueOwner<const CAPACITY: usize> {
local_queues: [ChaseLevDeque<ScheduledJob>; PRIORITY_LEVELS],
shared: Arc<WorkerQueues<CAPACITY>>,
}
impl<const CAPACITY: usize> WorkerQueues<CAPACITY> {
pub(crate) fn new() -> (WorkerQueueOwner<CAPACITY>, Arc<Self>) {
let local_queues = std::array::from_fn(|_| ChaseLevDeque::new(CAPACITY));
let local_stealers = std::array::from_fn(|index| local_queues[index].stealer());
let shared = Arc::new(Self {
local_stealers,
injector: moirai_utils::queue::LockFreeQueue::with_capacity(INJECTOR_CAPACITY),
len: CacheAligned::new(AtomicUsize::new(0)),
});
(
WorkerQueueOwner {
local_queues,
shared: Arc::clone(&shared),
},
shared,
)
}
pub(crate) fn try_push_external(
&self,
priority: Priority,
job: ScheduledJob,
) -> Option<ScheduledJob> {
match self.injector.try_enqueue((priority, job)) {
Ok(()) => {
self.len.fetch_add(1, Ordering::Relaxed);
None
}
Err((_priority, job)) => Some(job),
}
}
pub(crate) fn steal_one(&self) -> Option<ScheduledJob> {
if self.len.load(Ordering::Relaxed) == 0 {
return None;
}
for &index in &PRIORITY_POP_ORDER {
loop {
match self.local_stealers[index].steal() {
StealResult::Success(job) => {
self.len.fetch_sub(1, Ordering::Relaxed);
return Some(job);
}
StealResult::Retry => continue,
StealResult::Empty => break,
}
}
}
if let Some((_priority, job)) = self.injector.try_dequeue() {
self.len.fetch_sub(1, Ordering::Relaxed);
return Some(job);
}
None
}
pub(crate) fn is_empty(&self) -> bool {
self.len.load(Ordering::Relaxed) == 0
}
#[cfg(test)]
pub(crate) fn len(&self) -> usize {
self.len.load(Ordering::Relaxed)
}
#[cfg(test)]
pub(crate) fn injector_capacity(&self) -> usize {
self.injector.capacity()
}
}
impl<const CAPACITY: usize> WorkerQueueOwner<CAPACITY> {
pub(crate) fn pop_local(&mut self) -> Option<ScheduledJob> {
if self.shared.len.load(Ordering::Relaxed) == 0 {
return None;
}
for &index in &PRIORITY_POP_ORDER {
if let Some(job) = self.local_queues[index].pop() {
self.shared.len.fetch_sub(1, Ordering::Relaxed);
return Some(job);
}
}
while let Some((priority, job)) = self.shared.injector.try_dequeue() {
self.local_queues[priority.index()].push(job);
}
for &index in &PRIORITY_POP_ORDER {
if let Some(job) = self.local_queues[index].pop() {
self.shared.len.fetch_sub(1, Ordering::Relaxed);
return Some(job);
}
}
None
}
pub(crate) fn steal_batch(&mut self, target: &WorkerQueues<CAPACITY>) -> Option<ScheduledJob> {
if target.len.load(Ordering::Relaxed) == 0 {
return None;
}
for &index in &PRIORITY_POP_ORDER {
loop {
match target.local_stealers[index].steal_batch() {
StealResult::Success(mut batch) => {
let first_job = batch
.next()
.expect("invariant: successful batch contains one job");
let mut pushed_count = 0;
for job in batch {
self.local_queues[index].push(job);
pushed_count += 1;
}
if pushed_count > 0 {
self.shared.len.fetch_add(pushed_count, Ordering::Relaxed);
}
target.len.fetch_sub(pushed_count + 1, Ordering::Relaxed);
return Some(first_job);
}
StealResult::Retry => continue,
StealResult::Empty => break,
}
}
}
if let Some((_priority, first_job)) = target.injector.try_dequeue() {
let mut pushed_count = 0;
while pushed_count < 15 {
if let Some((p, job)) = target.injector.try_dequeue() {
self.local_queues[p.index()].push(job);
pushed_count += 1;
} else {
break;
}
}
if pushed_count > 0 {
self.shared.len.fetch_add(pushed_count, Ordering::Relaxed);
}
target.len.fetch_sub(pushed_count + 1, Ordering::Relaxed);
return Some(first_job);
}
None
}
}
#[cfg(test)]
mod tests {
use std::sync::{Arc, Mutex};
use super::WorkerQueues;
use crate::schedule::job::ScheduledJob;
use moirai_core::Priority;
#[test]
fn worker_queue_pops_highest_priority_first() {
let observed = Arc::new(Mutex::new(Vec::new()));
let (mut owner, queues) = WorkerQueues::<256>::new();
for (priority, value) in [(Priority::Low, 1), (Priority::Critical, 2)] {
let observed = Arc::clone(&observed);
let () = queues
.try_push_external(
priority,
ScheduledJob::new(move |_| {
observed.lock().unwrap().push(value);
}),
)
.map_or((), |_| panic!("test queue has capacity"));
}
owner.pop_local().unwrap().execute(0);
owner.pop_local().unwrap().execute(0);
assert_eq!(*observed.lock().unwrap(), vec![2, 1]);
assert_eq!(queues.len(), 0);
}
#[test]
fn injector_uses_sane_default_capacity_not_65536() {
let (_owner, queues) = WorkerQueues::<256>::new();
assert_eq!(queues.injector_capacity(), super::INJECTOR_CAPACITY);
assert_eq!(queues.injector_capacity(), 1024);
assert_ne!(queues.injector_capacity(), 65536);
}
#[test]
fn injector_round_trips_through_external_push() {
let observed = Arc::new(Mutex::new(Vec::new()));
let (mut owner, queues) = WorkerQueues::<256>::new();
for (priority, value) in [(Priority::Normal, 7), (Priority::Critical, 9)] {
let observed = Arc::clone(&observed);
let () = queues
.try_push_external(
priority,
ScheduledJob::new(move |_| {
observed.lock().unwrap().push(value);
}),
)
.map_or((), |_| panic!("test queue has capacity"));
}
owner.pop_local().unwrap().execute(0);
owner.pop_local().unwrap().execute(0);
assert_eq!(*observed.lock().unwrap(), vec![9, 7]);
assert_eq!(queues.len(), 0);
}
#[test]
fn full_injector_returns_and_drops_rejected_job_once() {
let (_owner, queues) = WorkerQueues::<256>::new();
for _ in 0..super::INJECTOR_CAPACITY {
let () = queues
.try_push_external(Priority::Normal, ScheduledJob::new(|_| {}))
.map_or((), |_| panic!("capacity-sized admission must succeed"));
}
let capture = Arc::new(());
let rejected_capture = Arc::clone(&capture);
let rejected = queues
.try_push_external(
Priority::Normal,
ScheduledJob::new(move |_| drop(rejected_capture)),
)
.expect("one job beyond capacity must be rejected");
assert_eq!(queues.len(), super::INJECTOR_CAPACITY);
assert_eq!(Arc::strong_count(&capture), 2);
drop(rejected);
assert_eq!(Arc::strong_count(&capture), 1);
}
}