use std::collections::{BTreeMap, VecDeque};
use crate::queue::Task;
use crate::{ActorId, Priority};
use super::faults::SimRng;
fn actor_key(id: &ActorId) -> u128 {
id.0.as_u128()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SchedulingPolicy {
#[default]
Fifo,
Random,
RoundRobin,
}
pub(crate) struct SimTask {
pub ord: u64,
pub task: Task,
pub mids: Vec<u64>,
}
impl SimTask {
pub fn app_len(&self) -> usize {
self.mids.len()
}
pub fn ord(&self) -> u64 {
self.ord
}
}
pub(crate) struct ReadySet {
priority: VecDeque<SimTask>,
ready: VecDeque<SimTask>,
last_served: BTreeMap<u128, u64>,
queued_msgs: BTreeMap<u128, u64>,
}
impl ReadySet {
pub fn new() -> Self {
Self {
priority: VecDeque::new(),
ready: VecDeque::new(),
last_served: BTreeMap::new(),
queued_msgs: BTreeMap::new(),
}
}
pub fn push(&mut self, sim_task: SimTask) {
*self
.queued_msgs
.entry(actor_key(&sim_task.task.actor_id))
.or_insert(0) += sim_task.app_len() as u64;
if sim_task.task.priority >= Priority::High {
self.priority.push_back(sim_task);
} else {
self.ready.push_back(sim_task);
}
}
pub fn pick(
&mut self,
policy: SchedulingPolicy,
rng: &mut SimRng,
tick: u64,
) -> Option<SimTask> {
let mut picked = match self.priority.pop_front() {
Some(t) => t,
None => match policy {
SchedulingPolicy::Fifo => self.ready.pop_front()?,
SchedulingPolicy::Random => {
let len = self.ready.len();
if len == 0 {
return None;
}
let idx = rng.below(len as u64) as usize;
self.ready.remove(idx)?
}
SchedulingPolicy::RoundRobin => {
let mut best: Option<(u64, usize)> = None; for (idx, t) in self.ready.iter().enumerate() {
let served = self
.last_served
.get(&actor_key(&t.task.actor_id))
.copied()
.unwrap_or(0);
if best.is_none_or(|(bs, _)| served < bs) {
best = Some((served, idx));
}
}
let idx = best?.1;
self.ready.remove(idx)?
}
},
};
self.last_served
.insert(actor_key(&picked.task.actor_id), tick);
if let Some(q) = self.queued_msgs.get_mut(&actor_key(&picked.task.actor_id)) {
*q = q.saturating_sub(picked.app_len() as u64);
}
picked.task.additional_messages.shrink_to_fit();
Some(picked)
}
pub fn total_queued_msgs(&self) -> u64 {
self.queued_msgs.values().sum()
}
pub fn is_empty(&self) -> bool {
self.priority.is_empty() && self.ready.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Message, MessagePayload};
use uuid::Uuid;
fn test_id(n: u64) -> ActorId {
ActorId(Uuid::from_u128(0xA77C_70B1_5160_0000 + n as u128))
}
fn task_for(id: ActorId, prio: Priority, tag: u8) -> SimTask {
SimTask {
ord: 0,
task: Task {
actor_id: id,
message: Message {
sender: None,
payload: MessagePayload::Custom(vec![tag]),
priority: prio,
},
priority: prio,
additional_messages: Vec::new(),
},
mids: vec![tag as u64],
}
}
#[test]
fn priority_drains_before_ready_under_all_policies() {
let a = test_id(1);
let b = test_id(2);
for policy in [
SchedulingPolicy::Fifo,
SchedulingPolicy::Random,
SchedulingPolicy::RoundRobin,
] {
let mut set = ReadySet::new();
let mut rng = SimRng::new(1);
set.push(task_for(a, Priority::Normal, 1));
set.push(task_for(b, Priority::Critical, 2));
let first = set.pick(policy, &mut rng, 1).unwrap();
assert_eq!(first.task.priority, Priority::Critical);
let second = set.pick(policy, &mut rng, 2).unwrap();
assert_eq!(second.task.priority, Priority::Normal);
assert!(set.pick(policy, &mut rng, 3).is_none());
}
}
#[test]
fn fifo_preserves_order() {
let a = test_id(1);
let mut set = ReadySet::new();
let mut rng = SimRng::new(1);
set.push(task_for(a, Priority::Normal, 10));
set.push(task_for(a, Priority::Normal, 11));
assert_eq!(
set.pick(SchedulingPolicy::Fifo, &mut rng, 1).unwrap().mids[0],
10
);
assert_eq!(
set.pick(SchedulingPolicy::Fifo, &mut rng, 2).unwrap().mids[0],
11
);
}
#[test]
fn round_robin_serves_actors_fairly() {
let a = test_id(1);
let b = test_id(2);
let mut set = ReadySet::new();
let mut rng = SimRng::new(1);
set.push(task_for(a, Priority::Normal, 1));
set.push(task_for(a, Priority::Normal, 2));
set.push(task_for(b, Priority::Normal, 3));
set.push(task_for(b, Priority::Normal, 4));
let seq: Vec<u64> = (0..4)
.map(|t| {
set.pick(SchedulingPolicy::RoundRobin, &mut rng, t + 1)
.unwrap()
.mids[0]
})
.collect();
assert_eq!(
&seq[..],
&[1, 3, 2, 4],
"round-robin must alternate actors: got {seq:?}"
);
assert_eq!(set.total_queued_msgs(), 0);
}
}