use crate::ActError;
use crate::event::ProcessGate;
use crate::{
Result,
scheduler::{Process, Task},
};
use parking_lot::Mutex;
use std::sync::{
Arc,
atomic::{AtomicBool, AtomicUsize, Ordering},
};
use tokio::sync::mpsc;
#[derive(Debug, Clone)]
pub struct Queue {
senders: Arc<Vec<mpsc::Sender<QueueData>>>,
receivers: Arc<Mutex<Option<Vec<mpsc::Receiver<QueueData>>>>>,
gate: ProcessGate,
alive: Arc<AtomicBool>,
high_watermark: Arc<AtomicUsize>,
capacity: usize,
lane_capacity: usize,
}
#[derive(Debug)]
pub enum QueueData {
Task {
task: Arc<Task>,
proc: Arc<Process>,
},
Next {
task: Arc<Task>,
proc: Arc<Process>,
},
Abort,
}
impl Queue {
pub(crate) fn new(capacity: usize, gate: ProcessGate) -> Arc<Self> {
let capacity = capacity.max(1);
let lanes = gate.lanes();
let lane_capacity = (capacity / lanes).max(1);
let mut senders = Vec::with_capacity(lanes);
let mut receivers = Vec::with_capacity(lanes);
for _ in 0..lanes {
let (sender, receiver) = mpsc::channel::<QueueData>(lane_capacity);
senders.push(sender);
receivers.push(receiver);
}
Arc::new(Self {
senders: Arc::new(senders),
receivers: Arc::new(Mutex::new(Some(receivers))),
gate,
alive: Arc::new(AtomicBool::new(true)),
high_watermark: Arc::new(AtomicUsize::new(0)),
capacity,
lane_capacity,
})
}
pub fn lanes(&self) -> usize {
self.senders.len()
}
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn lane_capacity(&self) -> usize {
self.lane_capacity
}
pub fn depth(&self) -> usize {
self.buffered()
}
pub fn high_watermark(&self) -> usize {
self.high_watermark.load(Ordering::Acquire)
}
pub(crate) fn consumer_lease(&self) -> QueueConsumerLease {
QueueConsumerLease {
alive: self.alive.clone(),
}
}
pub(crate) fn take_receivers(&self) -> Vec<mpsc::Receiver<QueueData>> {
self.receivers.lock().take().unwrap_or_default()
}
pub(crate) fn send(&self, task: &Arc<Task>) -> Result<()> {
self.check_alive()?;
let proc = Self::item_proc(task)?;
self.try_push(
self.gate.lane_index(&task.pid),
QueueData::Task {
task: task.clone(),
proc,
},
)
}
pub(crate) fn send_next(&self, task: &Arc<Task>) -> Result<()> {
self.check_alive()?;
let proc = Self::item_proc(task)?;
self.try_push(
self.gate.lane_index(&task.pid),
QueueData::Next {
task: task.clone(),
proc,
},
)
}
fn item_proc(task: &Arc<Task>) -> Result<Arc<Process>> {
task.proc().ok_or_else(|| {
ActError::Runtime(format!(
"cannot dispatch task '{}:{}': its process was deallocated",
task.pid, task.id
))
})
}
fn check_alive(&self) -> Result<()> {
if !self.alive.load(Ordering::Acquire) {
return Err(ActError::Runtime(
"scheduler queue consumer is not running".to_string(),
));
}
Ok(())
}
fn try_push(&self, lane: usize, data: QueueData) -> Result<()> {
if let Err(err) = self.senders[lane].try_send(data) {
return Err(match err {
mpsc::error::TrySendError::Full(_) => ActError::QueueFull,
mpsc::error::TrySendError::Closed(_) => {
ActError::Runtime(format!("scheduler lane {lane} consumer is not running"))
}
});
}
self.record_accepted();
Ok(())
}
pub fn abort(&self) {
self.alive.store(false, Ordering::Release);
for lane in 0..self.senders.len() {
let _ = self.try_push(lane, QueueData::Abort);
}
}
fn buffered(&self) -> usize {
self.senders
.iter()
.map(|sender| sender.max_capacity() - sender.capacity())
.sum()
}
fn record_accepted(&self) {
self.high_watermark
.fetch_max(self.buffered(), Ordering::AcqRel);
}
#[cfg(test)]
fn push_for_test(&self, lane: usize, data: QueueData) -> Result<()> {
self.try_push(lane, data)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn a_full_lane_refuses_instead_of_buffering() {
let queue = Queue::new(4, ProcessGate::new(2));
assert_eq!(queue.lanes(), 2);
assert_eq!(queue.capacity(), 4);
assert_eq!(queue.lane_capacity(), 2);
queue.push_for_test(0, QueueData::Abort).unwrap();
queue.push_for_test(0, QueueData::Abort).unwrap();
assert_eq!(queue.depth(), 2);
assert_eq!(queue.high_watermark(), 2);
let err = queue.push_for_test(0, QueueData::Abort).unwrap_err();
assert!(matches!(err, ActError::QueueFull));
assert_eq!(queue.depth(), 2);
queue.push_for_test(1, QueueData::Abort).unwrap();
assert_eq!(queue.depth(), 3);
let mut receivers = queue.take_receivers();
assert!(matches!(
receivers[0].recv().await.unwrap(),
QueueData::Abort
));
assert_eq!(queue.depth(), 2);
assert!(queue.high_watermark() <= queue.capacity());
}
}
#[derive(Debug)]
pub(crate) struct QueueConsumerLease {
alive: Arc<AtomicBool>,
}
impl Drop for QueueConsumerLease {
fn drop(&mut self) {
self.alive.store(false, Ordering::Release);
}
}