use std::collections::{BTreeMap, HashMap};
use super::tcb::{
ApprovalId, ChannelId, LogicalDeadline, ResourceKey, SignalFilter, SubscriptionId, TaskId,
WaitCondition, WaitSet,
};
use crate::runtime::kernel::wire::EffectId;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum WaitKey {
Effect(EffectId),
Child(TaskId),
Approval(ApprovalId),
Signal(SignalFilter),
Timer(LogicalDeadline),
Channel(ChannelId),
Resource(ResourceKey),
External(SubscriptionId),
}
impl WaitKey {
pub(crate) fn keys_for(condition: &WaitCondition) -> Vec<WaitKey> {
match condition {
WaitCondition::Effect(id) => vec![WaitKey::Effect(id.clone())],
WaitCondition::Child(id) => vec![WaitKey::Child(id.clone())],
WaitCondition::Children(ids) => ids.iter().cloned().map(WaitKey::Child).collect(),
WaitCondition::Approval(id) => vec![WaitKey::Approval(id.clone())],
WaitCondition::Signal(filter) => vec![WaitKey::Signal(filter.clone())],
WaitCondition::Timer(deadline) => vec![WaitKey::Timer(*deadline)],
WaitCondition::Channel(id) => vec![WaitKey::Channel(id.clone())],
WaitCondition::Resource(key) => vec![WaitKey::Resource(key.clone())],
WaitCondition::External(id) => vec![WaitKey::External(id.clone())],
}
}
pub(crate) fn matches(&self, condition: &WaitCondition) -> bool {
Self::keys_for(condition).contains(self)
}
}
#[derive(Debug, Clone, Default)]
pub struct WaitIndex {
tasks_by_key: HashMap<WaitKey, Vec<TaskId>>,
timers: BTreeMap<u64, Vec<TaskId>>,
}
impl WaitIndex {
pub fn new() -> Self {
Self::default()
}
pub fn insert(&mut self, task_id: TaskId, condition: &WaitCondition) {
for key in WaitKey::keys_for(condition) {
if let WaitKey::Timer(LogicalDeadline(ms)) = key {
self.timers.entry(ms).or_default().push(task_id.clone());
}
let bucket = self.tasks_by_key.entry(key).or_default();
if !bucket.contains(&task_id) {
bucket.push(task_id.clone());
}
}
}
pub fn remove(&mut self, task_id: &TaskId, condition: &WaitCondition) {
for key in WaitKey::keys_for(condition) {
if let WaitKey::Timer(LogicalDeadline(ms)) = key
&& let Some(bucket) = self.timers.get_mut(&ms)
{
bucket.retain(|id| id != task_id);
if bucket.is_empty() {
self.timers.remove(&ms);
}
}
if let Some(bucket) = self.tasks_by_key.get_mut(&key) {
bucket.retain(|id| id != task_id);
if bucket.is_empty() {
self.tasks_by_key.remove(&key);
}
}
}
}
pub fn lookup(&self, key: &WaitKey) -> &[TaskId] {
self.tasks_by_key
.get(key)
.map(|ids| ids.as_slice())
.unwrap_or(&[])
}
pub fn wake(&mut self, key: &WaitKey) -> Vec<TaskId> {
let woken = self.tasks_by_key.remove(key).unwrap_or_default();
if let WaitKey::Timer(LogicalDeadline(ms)) = key {
self.timers.remove(ms);
}
woken
}
pub fn register_wait_set(&mut self, task_id: TaskId, wait_set: WaitSet) {
for condition in &wait_set.conditions {
self.insert(task_id.clone(), condition);
}
}
pub fn notify(&mut self, key: &WaitKey) -> Vec<TaskId> {
self.lookup(key).to_vec()
}
pub fn expire_timers(&mut self, now_ms: u64) -> Vec<TaskId> {
let due_deadlines: Vec<u64> = self.timers.range(..=now_ms).map(|(ms, _)| *ms).collect();
due_deadlines
.into_iter()
.flat_map(|ms| self.wake(&WaitKey::Timer(LogicalDeadline(ms))))
.collect()
}
pub(crate) fn due_timer_keys(&self, now_ms: u64) -> Vec<WaitKey> {
self.timers
.range(..=now_ms)
.map(|(ms, _)| WaitKey::Timer(LogicalDeadline(*ms)))
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scheduler::tcb::WaitCondition;
#[test]
fn insert_then_lookup_hits() {
let mut index = WaitIndex::new();
let effect = EffectId::new("e1").unwrap();
index.insert(
TaskId::from("task-1"),
&WaitCondition::Effect(effect.clone()),
);
assert_eq!(
index.lookup(&WaitKey::Effect(effect)),
&[TaskId::from("task-1")]
);
}
#[test]
fn remove_then_lookup_is_empty() {
let mut index = WaitIndex::new();
let effect = EffectId::new("e1").unwrap();
let condition = WaitCondition::Effect(effect.clone());
index.insert(TaskId::from("task-1"), &condition);
index.remove(&TaskId::from("task-1"), &condition);
assert!(index.lookup(&WaitKey::Effect(effect)).is_empty());
}
#[test]
fn two_tasks_waiting_on_the_same_effect_both_come_back() {
let mut index = WaitIndex::new();
let effect = EffectId::new("e1").unwrap();
let condition = WaitCondition::Effect(effect.clone());
index.insert(TaskId::from("task-1"), &condition);
index.insert(TaskId::from("task-2"), &condition);
let hits = index.lookup(&WaitKey::Effect(effect));
assert_eq!(hits.len(), 2);
assert!(hits.contains(&TaskId::from("task-1")));
assert!(hits.contains(&TaskId::from("task-2")));
}
use crate::scheduler::tcb::{WaitMode, WaitSet};
#[test]
fn wait_set_registration_indexes_every_condition_without_owning_satisfaction() {
let mut index = WaitIndex::new();
let e1 = EffectId::new("e1").unwrap();
let e2 = EffectId::new("e2").unwrap();
let wait_set = WaitSet {
mode: WaitMode::Any,
conditions: vec![
WaitCondition::Effect(e1.clone()),
WaitCondition::Effect(e2.clone()),
],
};
index.register_wait_set(TaskId::from("task-1"), wait_set);
assert_eq!(
index.notify(&WaitKey::Effect(e1)),
vec![TaskId::from("task-1")]
);
assert_eq!(
index.lookup(&WaitKey::Effect(e2)),
&[TaskId::from("task-1")],
"the reverse index reports candidates; the TCB owns satisfaction and cleanup"
);
}
#[test]
fn notify_is_a_non_mutating_reverse_lookup_for_all_mode_too() {
let mut index = WaitIndex::new();
let e1 = EffectId::new("e1").unwrap();
let e2 = EffectId::new("e2").unwrap();
let wait_set = WaitSet {
mode: WaitMode::All,
conditions: vec![
WaitCondition::Effect(e1.clone()),
WaitCondition::Effect(e2.clone()),
],
};
index.register_wait_set(TaskId::from("task-1"), wait_set);
assert_eq!(
index.notify(&WaitKey::Effect(e1.clone())),
&[TaskId::from("task-1")]
);
assert_eq!(
index.notify(&WaitKey::Effect(e2)),
vec![TaskId::from("task-1")]
);
assert_eq!(
index.notify(&WaitKey::Effect(e1)),
vec![TaskId::from("task-1")],
"dedupe is durable TCB state, not reverse-index state"
);
}
#[test]
fn heterogeneous_wait_set_registers_child_and_timer_keys() {
let mut index = WaitIndex::new();
let wait_set = WaitSet {
mode: WaitMode::Any,
conditions: vec![
WaitCondition::Child(TaskId::from("child-1")),
WaitCondition::Timer(LogicalDeadline(1_000)),
],
};
index.register_wait_set(TaskId::from("task-1"), wait_set);
assert_eq!(
index.notify(&WaitKey::Child(TaskId::from("child-1"))),
vec![TaskId::from("task-1")]
);
assert_eq!(
index.lookup(&WaitKey::Timer(LogicalDeadline(1_000))),
&[TaskId::from("task-1")]
);
}
}