sched: remove force_resched()

This function is used to force the scheduler to reschedule another task,
avoiding the fast-path exit.  Given the sheer number of task state
change points in the code, the fast-path exit code has become too
brittle.

Instead, check that the current task's state is still `Running` before
taking the fast-path short-circuit. The cost of one spinlock uncontended
lock-unlock cycle is worth the cost of avoiding many subtle scheduling
logic bugs.
This commit is contained in:
Matthew Leach
2026-01-16 20:10:32 +00:00
parent 4a88739804
commit ecdfd360d1
2 changed files with 9 additions and 30 deletions

View File

@@ -76,12 +76,6 @@ fn schedule() {
SCHED_STATE.borrow_mut().do_schedule();
}
/// Set the force resched task for this CPU. This ensures that the next time
/// schedule() is called a full run of the schduling algorithm will occur.
fn force_resched() {
SCHED_STATE.borrow_mut().force_resched = true;
}
pub fn spawn_kernel_work(fut: impl Future<Output = ()> + 'static + Send) {
current_task().ctx.put_kernel_work(Box::pin(fut));
}
@@ -282,15 +276,12 @@ impl SchedState {
needs_resched = true;
}
if !needs_resched {
// Fast Path: Only return if we have a valid task, it has budget,
// AND it's not the idle task.
//
// Ensure that, in a debug build, we are only taking the fast-path
// on a *running* task.
if let Some(current) = self.run_q.current_mut() {
debug_assert_eq!(*current.state.lock_save_irq(), TaskState::Running);
}
if !needs_resched
&& let Some(current) = self.run_q.current()
&& matches!(*current.state.lock_save_irq(), TaskState::Running)
{
// Fast Path: Only return if we have a valid task (Running state),
// it has budget, AND it's not the idle task.
return;
}

View File

@@ -1,4 +1,4 @@
use super::{current::current_task, force_resched, schedule, waker::create_waker};
use super::{current::current_task, schedule, waker::create_waker};
use crate::{
arch::{Arch, ArchImpl},
process::{
@@ -128,7 +128,6 @@ pub fn dispatch_userspace_task(ctx: *mut UserCtx) {
// task.
// Task is currently running or is runnable and will now sleep.
TaskState::Running | TaskState::Runnable => {
force_resched();
*task_state = TaskState::Sleeping;
}
// If we were woken between the future returning
@@ -142,9 +141,7 @@ pub fn dispatch_userspace_task(ctx: *mut UserCtx) {
// If the task finished concurrently while we were
// polling its signal work, let the scheduler
// pick another task; no further work to do here.
TaskState::Finished => {
force_resched();
}
TaskState::Finished => {}
// We should never get here for any other state.
s => {
unreachable!(
@@ -178,9 +175,6 @@ pub fn dispatch_userspace_task(ctx: *mut UserCtx) {
// find another task to execute, removing this task
// from the runqueue, reaping it's resouces.
if task.state.lock_save_irq().is_finished() {
// Ensure we don't take the fast-path sched exit
// for a finished task.
force_resched();
state = State::PickNewTask;
continue;
}
@@ -207,7 +201,6 @@ pub fn dispatch_userspace_task(ctx: *mut UserCtx) {
match *task_state {
// Task is runnable or running, put it to sleep.
TaskState::Running | TaskState::Runnable => {
force_resched();
*task_state = TaskState::Sleeping
}
// If we were woken between the future returning
@@ -221,9 +214,7 @@ pub fn dispatch_userspace_task(ctx: *mut UserCtx) {
// Task finished concurrently while we were trying
// to put it to sleep; just reschedule and let
// teardown handle it.
TaskState::Finished => {
force_resched();
}
TaskState::Finished => {}
// We should never get here for any other state.
s => {
unreachable!(
@@ -255,8 +246,6 @@ pub fn dispatch_userspace_task(ctx: *mut UserCtx) {
ptrace.set_waker(create_waker(task.descriptor()));
*task.state.lock_save_irq() = TaskState::Stopped;
force_resched();
state = State::PickNewTask;
continue 'dispatch;
}
@@ -299,7 +288,6 @@ pub fn dispatch_userspace_task(ctx: *mut UserCtx) {
}
}
force_resched();
state = State::PickNewTask;
continue 'dispatch;
}