deepstrike_core/scheduler/
wait_index.rs1use std::collections::{BTreeMap, HashMap};
5
6use super::tcb::{
7 ApprovalId, ChannelId, LogicalDeadline, ResourceKey, SignalFilter, SubscriptionId, TaskId,
8 WaitCondition, WaitSet,
9};
10use crate::runtime::kernel::wire::EffectId;
11
12#[derive(Debug, Clone, PartialEq, Eq, Hash)]
16pub enum WaitKey {
17 Effect(EffectId),
18 Child(TaskId),
19 Approval(ApprovalId),
20 Signal(SignalFilter),
21 Timer(LogicalDeadline),
22 Channel(ChannelId),
23 Resource(ResourceKey),
24 External(SubscriptionId),
25}
26
27impl WaitKey {
28 pub(crate) fn keys_for(condition: &WaitCondition) -> Vec<WaitKey> {
29 match condition {
30 WaitCondition::Effect(id) => vec![WaitKey::Effect(id.clone())],
31 WaitCondition::Child(id) => vec![WaitKey::Child(id.clone())],
32 WaitCondition::Children(ids) => ids.iter().cloned().map(WaitKey::Child).collect(),
33 WaitCondition::Approval(id) => vec![WaitKey::Approval(id.clone())],
34 WaitCondition::Signal(filter) => vec![WaitKey::Signal(filter.clone())],
35 WaitCondition::Timer(deadline) => vec![WaitKey::Timer(*deadline)],
36 WaitCondition::Channel(id) => vec![WaitKey::Channel(id.clone())],
37 WaitCondition::Resource(key) => vec![WaitKey::Resource(key.clone())],
38 WaitCondition::External(id) => vec![WaitKey::External(id.clone())],
39 }
40 }
41
42 pub(crate) fn matches(&self, condition: &WaitCondition) -> bool {
43 Self::keys_for(condition).contains(self)
44 }
45}
46
47#[derive(Debug, Clone, Default)]
51pub struct WaitIndex {
52 tasks_by_key: HashMap<WaitKey, Vec<TaskId>>,
53 timers: BTreeMap<u64, Vec<TaskId>>,
57}
58
59impl WaitIndex {
60 pub fn new() -> Self {
61 Self::default()
62 }
63
64 pub fn insert(&mut self, task_id: TaskId, condition: &WaitCondition) {
65 for key in WaitKey::keys_for(condition) {
66 if let WaitKey::Timer(LogicalDeadline(ms)) = key {
67 self.timers.entry(ms).or_default().push(task_id.clone());
68 }
69 let bucket = self.tasks_by_key.entry(key).or_default();
70 if !bucket.contains(&task_id) {
71 bucket.push(task_id.clone());
72 }
73 }
74 }
75
76 pub fn remove(&mut self, task_id: &TaskId, condition: &WaitCondition) {
77 for key in WaitKey::keys_for(condition) {
78 if let WaitKey::Timer(LogicalDeadline(ms)) = key
79 && let Some(bucket) = self.timers.get_mut(&ms)
80 {
81 bucket.retain(|id| id != task_id);
82 if bucket.is_empty() {
83 self.timers.remove(&ms);
84 }
85 }
86 if let Some(bucket) = self.tasks_by_key.get_mut(&key) {
87 bucket.retain(|id| id != task_id);
88 if bucket.is_empty() {
89 self.tasks_by_key.remove(&key);
90 }
91 }
92 }
93 }
94
95 pub fn lookup(&self, key: &WaitKey) -> &[TaskId] {
96 self.tasks_by_key
97 .get(key)
98 .map(|ids| ids.as_slice())
99 .unwrap_or(&[])
100 }
101
102 pub fn wake(&mut self, key: &WaitKey) -> Vec<TaskId> {
108 let woken = self.tasks_by_key.remove(key).unwrap_or_default();
109 if let WaitKey::Timer(LogicalDeadline(ms)) = key {
110 self.timers.remove(ms);
111 }
112 woken
113 }
114
115 pub fn register_wait_set(&mut self, task_id: TaskId, wait_set: WaitSet) {
120 for condition in &wait_set.conditions {
121 self.insert(task_id.clone(), condition);
122 }
123 }
124
125 pub fn notify(&mut self, key: &WaitKey) -> Vec<TaskId> {
133 self.lookup(key).to_vec()
134 }
135
136 pub fn expire_timers(&mut self, now_ms: u64) -> Vec<TaskId> {
141 let due_deadlines: Vec<u64> = self.timers.range(..=now_ms).map(|(ms, _)| *ms).collect();
142 due_deadlines
143 .into_iter()
144 .flat_map(|ms| self.wake(&WaitKey::Timer(LogicalDeadline(ms))))
145 .collect()
146 }
147
148 pub(crate) fn due_timer_keys(&self, now_ms: u64) -> Vec<WaitKey> {
151 self.timers
152 .range(..=now_ms)
153 .map(|(ms, _)| WaitKey::Timer(LogicalDeadline(*ms)))
154 .collect()
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161 use crate::scheduler::tcb::WaitCondition;
162
163 #[test]
164 fn insert_then_lookup_hits() {
165 let mut index = WaitIndex::new();
166 let effect = EffectId::new("e1").unwrap();
167 index.insert(
168 TaskId::from("task-1"),
169 &WaitCondition::Effect(effect.clone()),
170 );
171
172 assert_eq!(
173 index.lookup(&WaitKey::Effect(effect)),
174 &[TaskId::from("task-1")]
175 );
176 }
177
178 #[test]
179 fn remove_then_lookup_is_empty() {
180 let mut index = WaitIndex::new();
181 let effect = EffectId::new("e1").unwrap();
182 let condition = WaitCondition::Effect(effect.clone());
183 index.insert(TaskId::from("task-1"), &condition);
184
185 index.remove(&TaskId::from("task-1"), &condition);
186
187 assert!(index.lookup(&WaitKey::Effect(effect)).is_empty());
188 }
189
190 #[test]
191 fn two_tasks_waiting_on_the_same_effect_both_come_back() {
192 let mut index = WaitIndex::new();
193 let effect = EffectId::new("e1").unwrap();
194 let condition = WaitCondition::Effect(effect.clone());
195 index.insert(TaskId::from("task-1"), &condition);
196 index.insert(TaskId::from("task-2"), &condition);
197
198 let hits = index.lookup(&WaitKey::Effect(effect));
199 assert_eq!(hits.len(), 2);
200 assert!(hits.contains(&TaskId::from("task-1")));
201 assert!(hits.contains(&TaskId::from("task-2")));
202 }
203
204 use crate::scheduler::tcb::{WaitMode, WaitSet};
205
206 #[test]
207 fn wait_set_registration_indexes_every_condition_without_owning_satisfaction() {
208 let mut index = WaitIndex::new();
209 let e1 = EffectId::new("e1").unwrap();
210 let e2 = EffectId::new("e2").unwrap();
211 let wait_set = WaitSet {
212 mode: WaitMode::Any,
213 conditions: vec![
214 WaitCondition::Effect(e1.clone()),
215 WaitCondition::Effect(e2.clone()),
216 ],
217 };
218 index.register_wait_set(TaskId::from("task-1"), wait_set);
219
220 assert_eq!(
221 index.notify(&WaitKey::Effect(e1)),
222 vec![TaskId::from("task-1")]
223 );
224 assert_eq!(
225 index.lookup(&WaitKey::Effect(e2)),
226 &[TaskId::from("task-1")],
227 "the reverse index reports candidates; the TCB owns satisfaction and cleanup"
228 );
229 }
230
231 #[test]
232 fn notify_is_a_non_mutating_reverse_lookup_for_all_mode_too() {
233 let mut index = WaitIndex::new();
234 let e1 = EffectId::new("e1").unwrap();
235 let e2 = EffectId::new("e2").unwrap();
236 let wait_set = WaitSet {
237 mode: WaitMode::All,
238 conditions: vec![
239 WaitCondition::Effect(e1.clone()),
240 WaitCondition::Effect(e2.clone()),
241 ],
242 };
243 index.register_wait_set(TaskId::from("task-1"), wait_set);
244
245 assert_eq!(
246 index.notify(&WaitKey::Effect(e1.clone())),
247 &[TaskId::from("task-1")]
248 );
249 assert_eq!(
250 index.notify(&WaitKey::Effect(e2)),
251 vec![TaskId::from("task-1")]
252 );
253 assert_eq!(
254 index.notify(&WaitKey::Effect(e1)),
255 vec![TaskId::from("task-1")],
256 "dedupe is durable TCB state, not reverse-index state"
257 );
258 }
259
260 #[test]
261 fn heterogeneous_wait_set_registers_child_and_timer_keys() {
262 let mut index = WaitIndex::new();
264 let wait_set = WaitSet {
265 mode: WaitMode::Any,
266 conditions: vec![
267 WaitCondition::Child(TaskId::from("child-1")),
268 WaitCondition::Timer(LogicalDeadline(1_000)),
269 ],
270 };
271 index.register_wait_set(TaskId::from("task-1"), wait_set);
272
273 assert_eq!(
274 index.notify(&WaitKey::Child(TaskId::from("child-1"))),
275 vec![TaskId::from("task-1")]
276 );
277 assert_eq!(
278 index.lookup(&WaitKey::Timer(LogicalDeadline(1_000))),
279 &[TaskId::from("task-1")]
280 );
281 }
282}