[PATCH v4 07/26] sched/deadline: Do not lose a throttled task switched out and back
From: Mathieu Desnoyers
Date: Fri Nov 06 2026 - 20:48:31 EST
A SCHED_DEADLINE task that is throttled, switched to another policy and
switched back before its 0-lag time can be left throttled with nothing
to replenish it: on no dl_rq and with no timer pending, it never runs
again, not even for a fatal signal, until another switch out of
SCHED_DEADLINE requeues it:
1. the task exhausts its runtime: dl_throttled is set, and the
replenishment timer armed;
2. a switch to SCHED_OTHER arms the inactive timer for the 0-lag time,
task_non_contending(), the parameters staying until then;
3. the replenishment timer fires, finds a task out of the class and
returns, dl_throttled still set;
4. a switch back before the 0-lag time cancels the inactive timer, and
enqueue_dl_entity() leaves a throttled task to its replenishment
timer, which fired already.
The window is the overrun scaled by period/runtime: about 100us for an
overrun of 20us at 20% bandwidth. The replenishment timer reset a task
it found out of the class until commit 209a0cbda7a0 ("sched/deadline:
Improve the tracking of active utilization") moved that reset to the
inactive timer.
Give the task the replenishment the timer owes it, as the timer does for
a throttled task that is not queued: the overrun comes off the periods
that follow, the deadline moves past it, and dl_throttled and dl_yielded
are cleared. Clearing dl_throttled alone, as the first version of this
patch did, left the overrun, the deadline and a yield to throttle the
task again, as the Sashiko AI review of the first posting of this series
pointed out.
The deadline_switch_test selftest added later in this series fails its
throttled_round_trip case 8 times out of 8 on v7.3 and v7.4-rc1, and
passes it 8 of 8 with this change. A cgroup selftest under development
found it, hanging about once in 1350 runs.
Fixes: 209a0cbda7a0 ("sched/deadline: Improve the tracking of active utilization")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers@xxxxxxxxxxxx>
---
kernel/sched/deadline.c | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c
index 00983487bff3..6cf33045d93a 100644
--- a/kernel/sched/deadline.c
+++ b/kernel/sched/deadline.c
@@ -1232,10 +1232,23 @@ static enum hrtimer_restart dl_task_timer(struct hrtimer *timer)
/*
* The task might have changed its scheduling policy to something
- * different than SCHED_DEADLINE (through switched_from_dl()).
+ * different than SCHED_DEADLINE (through switched_from_dl()). It
+ * keeps its parameters until its 0-lag time and may come back to
+ * SCHED_DEADLINE before then, switched_to_dl() cancelling the
+ * inactive timer: give it the replenishment this timer owes it, as
+ * the !queued case below does, and leave the enqueue to its return.
+ * A throttle left behind would never be lifted; an overrun left in
+ * its runtime would be forgiven by the new period its enqueue
+ * starts; and a yield left set would throttle it again at once.
*/
- if (!dl_task(p))
+ if (!dl_task(p)) {
+ if (dl_se->dl_throttled && !is_dl_boosted(dl_se)) {
+ sched_clock_tick();
+ update_rq_clock(rq);
+ replenish_dl_entity(dl_se);
+ }
goto unlock;
+ }
/*
* The task might have been boosted by someone else and might be in the
--
2.47.3