[PATCH v5 17/29] sched/deadline: Leave a dying task out of a rebuild of the accounting

From: Mathieu Desnoyers

Date: Sun Nov 08 2026 - 14:01:10 EST


A rebuild of the root domains' accounting takes the pi_lock of each
deadline task, not its rq lock. A dying task past its 0-lag time
releases its bandwidth at its last schedule. It holds its rq lock there,
not its pi_lock. Then it clears dl_bw. The rebuild can find the task
between the two steps.

In this diagram, T is a deadline task that exits on CPU 0 past its 0-lag
time. bw is its dl_bw. CPU 1 rebuilds the accounting:

CPU 0 (T) CPU 1 (rebuild)

do_task_dead(): TASK_DEAD, under pi_lock
__schedule(): T's rq lock taken
task_non_contending(T)
dl_task_release_bw(T)
total_bw -= bw
dl_bw_gen recorded
record cleared
dl_rebuild_rd_accounting()
dl_clear_root_domains()
dl_bw_gen++
total_bw reset
dl_add_task_root_domain(T)
T's pi_lock taken
dl_bw != 0, dl_bw_gen old
total_bw += bw
__dl_clear_params(T): dl_bw = 0

dl_bw_gen does not help, since the release came before the rebuild. The
rebuild would count a bandwidth that nothing releases again. The root
domain would then refuse deadline tasks it has room for, until the next
rebuild.

Fix this with the record of the bandwidth, dl_bw_cpu. The release
clears it under dl_departed_lock, which the walk holds. So leave out a
dead task with no record. A dead task with a record has not reached its
last schedule. The walk counts it as a live task. That schedule then
releases the bandwidth, or puts the task on the departed list. A dead
task on the departed list keeps its bandwidth until its timer, and the
rebuild counts it as before.

The Sashiko AI review of v2 of this series found this window. Its review
of v4 found a gap in the first version of this fix. That version also
left out a task between TASK_DEAD and its last schedule, with its 0-lag
time ahead.

Fixes: f9a25f776d78 ("cpusets: Rebuild root domain deadline accounting information")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers@xxxxxxxxxxxx>
---
kernel/sched/deadline.c | 11 +++++++++++
1 file changed, 11 insertions(+)

diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c
index 4b3713e192b9..84102368822c 100644
--- a/kernel/sched/deadline.c
+++ b/kernel/sched/deadline.c
@@ -3727,6 +3727,17 @@ void dl_add_task_root_domain(struct task_struct *p)
if (recorded < 0 || !cpu_active(recorded))
goto unlock;
cpu = recorded;
+ } else if (READ_ONCE(p->__state) == TASK_DEAD && p->dl.dl_bw_cpu < 0) {
+ /*
+ * Count nothing for a dead task with no record. Its last
+ * schedule released the bandwidth under its rq lock, and has
+ * not cleared dl_bw yet. The release clears the record under
+ * dl_departed_lock, which this walk holds. A dead task with a
+ * record has not reached its last schedule. Count it as a
+ * live task. That schedule then releases what the walk
+ * counted, or puts the task on the departed list.
+ */
+ goto unlock;
}
rq = cpu_rq(cpu);
dl_b = &rq->rd->dl_bw;
--
2.47.3