[PATCH 19/17] sched_ext: Fix the stall watchdog blaming a bypassing owner for a stuck task

From: Tejun Heo

Date: Fri Oct 23 2026 - 16:21:51 EST


check_rq_for_timeouts() blames a stall on the scheduler whose DSQ the task
sits on, and on the task's owner when the DSQ has none. A bypassed task can
sit on the rescue DSQ or the local DSQ, and the blame then lands on its
owner. The owner is bypassing and places nothing. The host, the nearest
ancestor that is not bypassing, runs the bypassed tasks.

That matters when the owner is already exiting, during its own disable for
one. __scx_exit() on it is a no-op, the watchdog still ends its pass, and
nothing is ejected. If the host is not running the bypassed tasks, the
owner's disable, which waits on them, may never finish.

Blame the host for a stuck task whose owner is bypassing. Ejecting the host
re-hosts the bypassed subtree one level up. A task whose owner is not
bypassing keeps the blame on the owner, and with every ancestor bypassing,
scx_exec_sched() gives the root. A task on the host's bypass DSQ already led
to the host through dsq->sched.

The rescue overload accounting keeps charging the owner. Overload going
unanswered while the owner exits is acceptable.

Fixes: 2c91169377f4 ("sched_ext: Blame the DSQ's owning scheduler for a runnable stall")
Reported-by: Sashiko <sashiko-bot@xxxxxxxxxx>
Signed-off-by: Tejun Heo <tj@xxxxxxxxxx>
---
kernel/sched/ext/ext.c | 7 +++++++
1 file changed, 7 insertions(+)

diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c
index 8241cf374ef9..ae5e8861ca0a 100644
--- a/kernel/sched/ext/ext.c
+++ b/kernel/sched/ext/ext.c
@@ -4376,9 +4376,16 @@ static bool check_rq_for_timeouts(struct rq *rq)
* ancestor's bypass DSQ while the owner is bypassing.
* Blame the drainer. The local DSQ is consumed by the
* cpu itself and keeps blame on the owner.
+ *
+ * A bypassing owner places nothing, and its tasks run
+ * through the host even from the rescue DSQ or the
+ * local DSQ. Blame the host. Blaming an owner that is
+ * already exiting would eject nothing.
*/
if (dsq && dsq->sched && dsq->id != SCX_DSQ_LOCAL)
sch = dsq->sched;
+ else if (scx_bypassing(sch, cpu_of(rq)))
+ sch = scx_exec_sched(sch, cpu_of(rq));

__scx_exit(sch, SCX_EXIT_ERROR_STALL, 0, cpu_of(rq),
"%s[%d] failed to run for %u.%03us",
--
2.56.0