mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v1] sched/deadline: Fix this_bw on class change of a dying task
@ 2026-10-25 12:46 Nickolai Zeldovich
  2026-10-27  9:21 ` Juri Lelli
  0 siblings, 1 reply; 3+ messages in thread
From: Nickolai Zeldovich @ 2026-10-25 12:46 UTC (permalink / raw)
  To: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot
  Cc: Dietmar Eggemann, Valentin Schneider, K Prateek Nayak,
	Luca Abeni, linux-kernel, Nickolai Zeldovich

Moving a SCHED_DEADLINE task out of the class with sched_setscheduler(2)
after it stored TASK_DEAD in do_task_dead(), but before __schedule()
dequeued it, subtracts its bandwidth from the runqueue's this_bw twice.
this_bw then stays short until reboot: __sub_rq_bw() warns about the
underflow or about running_bw > this_bw, and GRUB reclaim for
SCHED_FLAG_RECLAIM tasks on that CPU is miscomputed.

The class change dequeues the still queued task with DEQUEUE_SAVE, which
removes its bandwidth from running_bw and this_bw. switched_from_dl()
then calls task_non_contending(), which, if the 0-lag time has passed,
removes it from this_bw again because the task is TASK_DEAD. That
removal is meant for the dying task's own DEQUEUE_SLEEP dequeue, which
has removed nothing before it.

Do the TASK_DEAD removal only on that dequeue path.

Reproduced under QEMU on x86_64 and riscv64 by moving exiting deadline
tasks to SCHED_OTHER in a loop; with this patch there is no warning.

Fixes: 8fd27231c330 ("sched/deadline: Track the "total rq utilization" too")
Signed-off-by: Nickolai Zeldovich <nickolai@csail.mit.edu>
---

Notes:
    Reproducer (root or CAP_SYS_NICE, two or more CPUs; the warnings are
    WARN_ON_ONCE, so boot afresh for each run):
    
      gcc -O2 -o dl-dead-short dl-dead-short.c && ./dl-dead-short
      dmesg | grep 'WARNING:.*deadline'
    
    Before (x86_64 under QEMU TCG, -smp 2; the first hit after about one
    second, on riscv64 after about a minute):
    
      WARNING: kernel/sched/deadline.c:257 at task_non_contending+0x3e9/0x430, CPU#0: dl-dead-short/68
      Call Trace:
       switched_from_dl+0x2d/0x1d0
       sched_change_begin+0x1f2/0x240
       __sched_setscheduler+0x5c6/0x820
      WARNING: kernel/sched/deadline.c:260 at task_non_contending+0x41a/0x430, CPU#0: dl-dead-short/68
      WARNING: kernel/sched/deadline.c:221 at enqueue_dl_entity+0x597/0x720, CPU#1: dl-dead-short/953
      WARNING: kernel/sched/deadline.c:260 at switched_from_dl+0x1a5/0x1d0, CPU#0: dl-dead-short/68
      WARNING: kernel/sched/deadline.c:260 at dequeue_dl_entity+0x276/0x2b0, CPU#0: dl-dead-short/68
    
    After: no warning in the 50000 iterations, x86_64 and riscv64.
    
      #define _GNU_SOURCE
      #include <sched.h>
      #include <stdint.h>
      #include <stdio.h>
      #include <stdlib.h>
      #include <unistd.h>
      #include <sys/syscall.h>
      #include <sys/wait.h>
    
      #ifndef SCHED_DEADLINE
      #define SCHED_DEADLINE 6
      #endif
      #define MS 1000000ULL
    
      struct attr {
      	uint32_t size, policy;
      	uint64_t flags;
      	int32_t nice;
      	uint32_t prio;
      	uint64_t runtime, deadline, period;
      };
    
      static void pin(int cpu)
      {
      	cpu_set_t s;
    
      	CPU_ZERO(&s);
      	for (int c = 0; c < CPU_SETSIZE; c++)
      		if (cpu < 0 || c == cpu)
      			CPU_SET(c, &s);
      	sched_setaffinity(0, sizeof(s), &s);
      }
    
      int main(void)
      {
      	struct attr a = { sizeof(a), SCHED_DEADLINE, 0, 0, 0,
      			  100 * MS, 200 * MS, 1000 * MS };
      	struct sched_param sp = { 0 };
    
      	for (int i = 0; i < 50000; i++) {
      		siginfo_t si;
      		int go[2];
      		pid_t pid;
      		char c = 0;
    
      		if (pipe(go))
      			return 1;
      		pin(1);
      		pid = fork();
      		if (pid == 0) {
      			if (read(go[0], &c, 1) != 1)	/* parent is on CPU 0 */
      				_exit(1);
      			pin(-1);
      			_exit(syscall(SYS_sched_setattr, 0, &a, 0) ? 1 : 0);
      		}
      		pin(0);
      		if (write(go[1], &c, 1) != 1)
      			return 1;
      		close(go[0]);
      		close(go[1]);
      		do {	/* wait for the zombie, then race its last schedule() */
      			si.si_pid = 0;
      			waitid(P_PID, pid, &si, WEXITED | WNOHANG | WNOWAIT);
      		} while (!si.si_pid);
      		for (volatile int k = rand() % 2000; k; k--)
      			;
      		sched_setscheduler(pid, SCHED_OTHER, &sp);
      		waitpid(pid, NULL, 0);
      		if (si.si_status) {
      			fprintf(stderr, "sched_setattr failed\n");
      			return 1;
      		}
      	}
      	return 0;
      }
    
    This patch was written with the assistance of an AI agent.

 kernel/sched/deadline.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c
index c0ebdcde5fe5d..dfae08625342e 100644
--- a/kernel/sched/deadline.c
+++ b/kernel/sched/deadline.c
@@ -444,7 +444,7 @@ static void task_non_contending(struct sched_dl_entity *dl_se, bool dl_task)
 			if (!dl_task || READ_ONCE(p->__state) == TASK_DEAD) {
 				struct dl_bw *dl_b = dl_bw_of(task_cpu(p));
 
-				if (READ_ONCE(p->__state) == TASK_DEAD)
+				if (dl_task && READ_ONCE(p->__state) == TASK_DEAD)
 					sub_rq_bw(dl_se, &rq->dl);
 				raw_spin_lock(&dl_b->lock);
 				__dl_sub(dl_b, dl_se->dl_bw, dl_bw_cpus(task_cpu(p)));

base-commit: 21a7f44fd0765fcc5194130b28767786eba1d1e7
-- 
2.53.0


^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-10-27 13:04 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-25 12:46 [PATCH v1] sched/deadline: Fix this_bw on class change of a dying task Nickolai Zeldovich
2026-10-27  9:21 ` Juri Lelli
2026-10-27 13:03   ` Nickolai Zeldovich

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®