use crossbeam_deque::{Injector, Steal, Stealer, Worker};
use crate::{ActorId, Message, Priority};
#[derive(Debug)]
pub struct Task {
pub actor_id: ActorId,
pub message: Message,
pub priority: Priority,
pub additional_messages: Vec<Message>,
}
pub struct WorkQueue {
injector: Injector<Task>,
worker_count: std::sync::atomic::AtomicUsize,
}
impl WorkQueue {
pub fn new() -> Self {
Self {
injector: Injector::new(),
worker_count: std::sync::atomic::AtomicUsize::new(0),
}
}
pub fn push(&self, task: Task) {
self.injector.push(task);
}
pub fn push_batch(&self, tasks: Vec<Task>) {
for task in tasks {
self.injector.push(task);
}
}
pub fn steal_global(&self) -> Option<Task> {
loop {
match self.injector.steal() {
Steal::Success(task) => return Some(task),
Steal::Empty => return None,
Steal::Retry => continue,
}
}
}
pub fn injector(&self) -> &Injector<Task> {
&self.injector
}
pub fn register_worker(&self) {
self.worker_count
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
pub fn unregister_worker(&self) {
self.worker_count
.fetch_sub(1, std::sync::atomic::Ordering::Relaxed);
}
}
impl Default for WorkQueue {
fn default() -> Self {
Self::new()
}
}
pub fn create_local_queue() -> (Worker<Task>, Stealer<Task>) {
let worker = Worker::new_fifo();
let stealer = worker.stealer();
(worker, stealer)
}
pub struct WorkStealer {
stealers: Vec<Stealer<Task>>,
index: std::sync::atomic::AtomicUsize,
}
impl WorkStealer {
pub fn new(stealers: Vec<Stealer<Task>>) -> Self {
Self {
stealers,
index: std::sync::atomic::AtomicUsize::new(0),
}
}
pub fn steal(&self) -> Option<Task> {
if self.stealers.is_empty() {
return None;
}
let start = self
.index
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let len = self.stealers.len();
for i in 0..len {
let idx = (start + i) % len;
loop {
match self.stealers[idx].steal() {
Steal::Success(task) => {
return Some(task);
}
Steal::Empty => break,
Steal::Retry => continue,
}
}
}
None
}
pub fn steal_batch(&self, dest: &Worker<Task>, max: usize) -> usize {
if self.stealers.is_empty() {
return 0;
}
let start = self
.index
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let len = self.stealers.len();
let mut stolen = 0;
for i in 0..len {
if stolen >= max {
break;
}
let idx = (start + i) % len;
loop {
match self.stealers[idx].steal_batch_and_pop(dest) {
Steal::Success(task) => {
dest.push(task);
stolen += 1;
}
Steal::Empty => break,
Steal::Retry => continue,
}
}
}
stolen
}
}
pub struct PriorityQueue {
critical: Injector<Task>,
high: Injector<Task>,
normal: Injector<Task>,
low: Injector<Task>,
}
impl PriorityQueue {
pub fn new() -> Self {
Self {
critical: Injector::new(),
high: Injector::new(),
normal: Injector::new(),
low: Injector::new(),
}
}
pub fn push(&self, task: Task) {
match task.priority {
Priority::Critical => self.critical.push(task),
Priority::High => self.high.push(task),
Priority::Normal => self.normal.push(task),
Priority::Low => self.low.push(task),
}
}
pub fn pop(&self) -> Option<Task> {
fn try_steal(injector: &Injector<Task>) -> Option<Task> {
loop {
match injector.steal() {
Steal::Success(task) => return Some(task),
Steal::Empty => return None,
Steal::Retry => continue,
}
}
}
try_steal(&self.critical)
.or_else(|| try_steal(&self.high))
.or_else(|| try_steal(&self.normal))
.or_else(|| try_steal(&self.low))
}
}
impl Default for PriorityQueue {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::MessagePayload;
#[test]
fn test_work_queue_basic() {
let queue = WorkQueue::new();
let task = Task {
actor_id: ActorId::new(),
message: Message {
sender: None,
payload: MessagePayload::Start,
priority: Priority::Normal,
},
priority: Priority::Normal,
additional_messages: Vec::new(),
};
queue.push(task);
let stolen = queue.steal_global();
assert!(stolen.is_some());
}
#[test]
fn test_local_queue() {
let (worker, _stealer) = create_local_queue();
let task = Task {
actor_id: ActorId::new(),
message: Message {
sender: None,
payload: MessagePayload::Start,
priority: Priority::Normal,
},
priority: Priority::Normal,
additional_messages: Vec::new(),
};
worker.push(task);
assert_eq!(worker.len(), 1);
let popped = worker.pop();
assert!(popped.is_some());
assert!(worker.is_empty());
}
#[test]
fn test_priority_queue() {
let queue = PriorityQueue::new();
let low_task = Task {
actor_id: ActorId::new(),
message: Message {
sender: None,
payload: MessagePayload::Start,
priority: Priority::Low,
},
priority: Priority::Low,
additional_messages: Vec::new(),
};
let high_task = Task {
actor_id: ActorId::new(),
message: Message {
sender: None,
payload: MessagePayload::Start,
priority: Priority::High,
},
priority: Priority::High,
additional_messages: Vec::new(),
};
queue.push(low_task);
queue.push(high_task);
let first = queue.pop().unwrap();
assert_eq!(first.priority, Priority::High);
let second = queue.pop().unwrap();
assert_eq!(second.priority, Priority::Low);
}
}