in src/scheduler/pct.rs [92:128]
fn next_task(&mut self, runnable: &[TaskId], current: Option<TaskId>, is_yielding: bool) -> Option<TaskId> {
// If any new tasks were created, assign them priorities at random
let known_tasks = self.priority_queue.len();
let max_tasks = usize::from(*runnable.iter().max().unwrap());
for tid in known_tasks..1 + max_tasks {
let index = self.rng.gen_range(0, self.priority_queue.len() + 1);
self.priority_queue.insert(index, TaskId::from(tid));
}
// No point doing priority changes when there's only one runnable task. This also means that
// our step counter is counting actual scheduling decisions, not no-ops where there was no
// choice about which task to run. From the paper (4.1, "Identifying Sequential Execution"):
// > Inserting priority change points during sequential execution is not necessary. The same
// > effect can be achieved by reducing the priority at the point the sequential thread
// > enables/creates a second thread.
// TODO is this really correct? need to think about it more
if runnable.len() > 1 {
if self.change_points.contains(&self.steps) || is_yielding {
// Deprioritize `current` by moving it to the end of the list
// TODO in the paper, the i'th change point gets priority i, whereas this gives d-i.
// TODO I don't think this matters, because the change points are randomized.
let current = current.expect("self.steps > 0 should mean a task has run");
let idx = self.priority_queue.iter().position(|tid| *tid == current).unwrap();
self.priority_queue.remove(idx);
self.priority_queue.push(current);
}
self.steps += 1;
if self.steps > self.max_steps {
self.max_steps = self.steps;
}
}
// Choose the highest-priority (== earliest in the queue) runnable task
Some(*self.priority_queue.iter().find(|tid| runnable.contains(tid)).unwrap())
}