in src/runtime/execution.rs [494:549]
fn schedule(&mut self) -> Result<(), String> {
// Don't schedule twice. If `maybe_yield` ran the scheduler, we don't want to run it
// again at the top of `step`.
if self.next_task != ScheduledTask::None {
return Ok(());
}
self.context_switches += 1;
match self.config.max_steps {
MaxSteps::FailAfter(n) if self.current_schedule.len() >= n => {
let msg = format!(
"exceeded max_steps bound {}. this might be caused by an unfair schedule (e.g., a spin loop)?",
n
);
return Err(msg);
}
MaxSteps::ContinueAfter(n) if self.current_schedule.len() >= n => {
self.next_task = ScheduledTask::Stopped;
return Ok(());
}
_ => {}
}
let mut unfinished_attached = false;
let runnable = self
.tasks
.iter()
.inspect(|t| unfinished_attached = unfinished_attached || (!t.finished() && !t.detached))
.filter(|t| t.runnable())
.map(|t| t.id)
.collect::<SmallVec<[_; DEFAULT_INLINE_TASKS]>>();
// We should finish execution when either
// (1) There are no runnable tasks, or
// (2) All runnable tasks have been detached AND there are no unfinished attached tasks
// If there are some unfinished attached tasks and all runnable tasks are detached, we must
// run some detached task to give them a chance to unblock some unfinished attached task.
if runnable.is_empty() || (!unfinished_attached && runnable.iter().all(|id| self.get(*id).detached)) {
self.next_task = ScheduledTask::Finished;
return Ok(());
}
let is_yielding = std::mem::replace(&mut self.has_yielded, false);
self.next_task = self
.scheduler
.borrow_mut()
.next_task(&runnable, self.current_task.id(), is_yielding)
.map(ScheduledTask::Some)
.unwrap_or(ScheduledTask::Stopped);
trace!(?runnable, next_task=?self.next_task);
Ok(())
}