[PATCH 2/4] sched/deadline: Fix total_bw leak when switching away from SCHED_DEADLINE while !queued
From: Juri Lelli
Date: Tue Nov 03 2026 - 08:21:45 EST
When a SCHED_DEADLINE task is switched to another policy while it is not
queued (sleeping or stopped), its bandwidth leaks in the root domain's
total_bw. The leak happens because:
- switched_from_dl() only calls task_non_contending() for queued tasks.
- For non-queued tasks, it subtracts running_bw and rq_bw, but never
releases total_bw from the root domain's dl_bw.
- The inactive timer is the only path that calls __dl_sub() to release
total_bw, but it is either not armed (the task never ran as DEADLINE)
or already fired while the task was still DEADLINE (only dropping
running_bw at that time).
Repeating this fills total_bw up to the admission cap, after which
sched_setattr(SCHED_DEADLINE) and CPU offline fail with -EBUSY.
Fix this by releasing total_bw directly in switched_from_dl() when the
task is not queued and dl_non_contending is not set (meaning no pending
inactive timer will handle it).
Fixes: 209a0cbda7a0 ("sched/deadline: Improve the tracking of active utilization")
Reported-by: Tejun Heo <tj@xxxxxxxxxx>
Closes: https://lore.kernel.org/all/2b5fb3f22bca6aa383be8f4781923392@xxxxxxxxxx/
Signed-off-by: Juri Lelli <juri.lelli@xxxxxxxxxx>
---
kernel/sched/deadline.c | 32 +++++++++++++++++++++++++-------
1 file changed, 25 insertions(+), 7 deletions(-)
diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c
index 68019dafbaf18..963fda6ddb7f6 100644
--- a/kernel/sched/deadline.c
+++ b/kernel/sched/deadline.c
@@ -3547,14 +3547,32 @@ static void switched_from_dl(struct rq *rq, struct task_struct *p)
dec_dl_tasks_cs(p);
if (!task_on_rq_queued(p)) {
- /*
- * Inactive timer is armed. However, p is leaving DEADLINE and
- * might migrate away from this rq while continuing to run on
- * some other class. We need to remove its contribution from
- * this rq running_bw now, or sub_rq_bw (below) will complain.
- */
- if (p->dl.dl_non_contending)
+ if (p->dl.dl_non_contending) {
+ /*
+ * Inactive timer is armed. However, p is leaving
+ * DEADLINE and might migrate away from this rq while
+ * continuing to run on some other class. We need to
+ * remove its contribution from this rq running_bw now,
+ * or sub_rq_bw (below) will complain.
+ *
+ * inactive_task_timer() will find !dl_task(p) and
+ * release total_bw via __dl_sub().
+ */
sub_running_bw(&p->dl, &rq->dl);
+ } else {
+ /*
+ * No pending inactive timer: the task never ran as
+ * DEADLINE or the timer already fired while still
+ * DEADLINE (only dropping running_bw at that time).
+ * Nobody else will release total_bw, do it now.
+ */
+ unsigned int cpu = dl_task_root_domain_cpu(p);
+ struct dl_bw *dl_b = &cpu_rq(cpu)->rd->dl_bw;
+
+ raw_spin_lock(&dl_b->lock);
+ __dl_sub(dl_b, p->dl.dl_bw, dl_bw_cpus(cpu));
+ raw_spin_unlock(&dl_b->lock);
+ }
sub_rq_bw(&p->dl, &rq->dl);
}
--
2.56.0