use std::sync::{
Arc,
atomic::{AtomicUsize, Ordering},
};
use moirai_core::Priority;
use moirai_scheduler::{ChaseLevDeque, ChaseLevStealer, DequeCapacity};
use moirai_utils::CacheAligned;
use super::priority::{PRIORITY_LEVELS, PRIORITY_POP_ORDER};
use super::steal::steal_after_contention;
use crate::schedule::job::ScheduledJob;
pub(crate) struct WorkerQueues {
local_stealers: [ChaseLevStealer<ScheduledJob>; PRIORITY_LEVELS],
injector: moirai_utils::queue::LockFreeQueue<(Priority, ScheduledJob)>,
len: CacheAligned<AtomicUsize>,
}
pub(crate) struct WorkerQueueOwner {
local_queues: [ChaseLevDeque<ScheduledJob>; PRIORITY_LEVELS],
base_capacities: [DequeCapacity<ScheduledJob>; PRIORITY_LEVELS],
shared: Arc<WorkerQueues>,
}
impl WorkerQueues {
pub(crate) fn new(
injector_capacity: usize,
local_queue_capacity: DequeCapacity<ScheduledJob>,
) -> (WorkerQueueOwner, Arc<Self>) {
let default_plane = Priority::default().index();
let base_capacities = std::array::from_fn(|plane| {
if plane == default_plane {
local_queue_capacity
} else {
DequeCapacity::minimum()
}
});
let local_queues = std::array::from_fn(|plane| ChaseLevDeque::new(base_capacities[plane]));
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,
base_capacities,
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 {
if let Some(job) = steal_after_contention(|| self.local_stealers[index].steal()) {
self.len.fetch_sub(1, Ordering::Relaxed);
return Some(job);
}
}
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()
}
#[cfg(test)]
pub(crate) fn local_queue_capacities(&self) -> [usize; PRIORITY_LEVELS] {
std::array::from_fn(|plane| self.local_stealers[plane].capacity())
}
}
impl WorkerQueueOwner {
pub(crate) fn shrink_drained_planes(&mut self) {
for (deque, &base) in self.local_queues.iter_mut().zip(&self.base_capacities) {
deque.shrink_to(base);
}
}
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) -> Option<ScheduledJob> {
if target.len.load(Ordering::Relaxed) == 0 {
return None;
}
for &index in &PRIORITY_POP_ORDER {
if let Some(mut batch) =
steal_after_contention(|| target.local_stealers[index].steal_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);
}
}
if let Some((_priority, first_job)) = target.injector.try_dequeue() {
let mut pushed_count = 0;
while pushed_count < 15 {
match target.injector.try_dequeue() {
Some((p, job)) => {
self.local_queues[p.index()].push(job);
pushed_count += 1;
}
_ => {
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
}
}