mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH sched/urgent] sched/deadline: Fix stale rq->dl_server after a server pick returns RETRY_TASK
@ 2026-10-30 22:18 Tejun Heo
  2026-10-30 23:56 ` Tejun Heo
  2026-10-31  1:19 ` Tejun Heo
  0 siblings, 2 replies; 4+ messages in thread
From: Tejun Heo @ 2026-10-30 22:18 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Juri Lelli, Vincent Guittot
  Cc: Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
	Valentin Schneider, K Prateek Nayak, linux-kernel, Tejun Heo

__pick_task_dl() records the server in rq->dl_server whenever the server's
pick returns non-NULL, RETRY_TASK included. __pick_next_task() clears
rq->dl_server at its top, but a RETRY_TASK restarts the pick below that
clear. put_prev_set_next_task() then hands the stale server to whatever the
restarted pick takes.

Since b3a2dfa8b42e ("sched/fair: Add newidle balance to pick_task_fair()"),
fair_server_pick_task() returns RETRY_TASK when the newidle balance released
the rq lock and a higher class task was enqueued meanwhile.

That task, an RT task for example, then carries the fair server in
p->dl_server until it is next put. The RV deadline monitors see the fair
server switch in with it, and core scheduling, if enabled meanwhile, treats
the task as deadline priority.

Don't record the server when its pick returns RETRY_TASK. The restarted pick
records whichever server it goes through, if any.

Verified with a synthetic reproducer.

Fixes: b3a2dfa8b42e ("sched/fair: Add newidle balance to pick_task_fair()")
Signed-off-by: Tejun Heo <tj@kernel.org>
---
 kernel/sched/deadline.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c
index c0ebdcde5fe5..cc9c7cb7dc42 100644
--- a/kernel/sched/deadline.c
+++ b/kernel/sched/deadline.c
@@ -2836,6 +2836,13 @@ static struct task_struct *__pick_task_dl(struct rq *rq, struct rq_flags *rf)
 			dl_server_stop(dl_se);
 			goto again;
 		}
+		/*
+		 * RETRY_TASK restarts the pick. Don't record @dl_se, or
+		 * put_prev_set_next_task() hands it to whatever the restarted
+		 * pick takes.
+		 */
+		if (p == RETRY_TASK)
+			return p;
 		rq->dl_server = dl_se;
 	} else {
 		p = dl_task_of(dl_se);
-- 
2.56.0


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

* Re: [PATCH sched/urgent] sched/deadline: Fix stale rq->dl_server after a server pick returns RETRY_TASK
  2026-10-30 22:18 [PATCH sched/urgent] sched/deadline: Fix stale rq->dl_server after a server pick returns RETRY_TASK Tejun Heo
@ 2026-10-30 23:56 ` Tejun Heo
  2026-10-31  1:19 ` Tejun Heo
  1 sibling, 0 replies; 4+ messages in thread
From: Tejun Heo @ 2026-10-30 23:56 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Juri Lelli, Vincent Guittot
  Cc: Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
	Valentin Schneider, K Prateek Nayak, linux-kernel

On Fri, Oct 30, 2026 at 12:18:42PM -1000, Tejun Heo wrote:
> __pick_task_dl() records the server in rq->dl_server whenever the server's
> pick returns non-NULL, RETRY_TASK included. __pick_next_task() clears
> rq->dl_server at its top, but a RETRY_TASK restarts the pick below that
> clear. put_prev_set_next_task() then hands the stale server to whatever the
> restarted pick takes.
> 
> Since b3a2dfa8b42e ("sched/fair: Add newidle balance to pick_task_fair()"),
> fair_server_pick_task() returns RETRY_TASK when the newidle balance released
> the rq lock and a higher class task was enqueued meanwhile.
> 
> That task, an RT task for example, then carries the fair server in
> p->dl_server until it is next put. The RV deadline monitors see the fair
> server switch in with it, and core scheduling, if enabled meanwhile, treats
> the task as deadline priority.
> 
> Don't record the server when its pick returns RETRY_TASK. The restarted pick
> records whichever server it goes through, if any.
> 
> Verified with a synthetic reproducer.
> 
> Fixes: b3a2dfa8b42e ("sched/fair: Add newidle balance to pick_task_fair()")
> Signed-off-by: Tejun Heo <tj@kernel.org>

Ugh, please ignore this one. Sent out the wrong patch. I'll test Juri's.

Thanks.

-- 
tejun

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

* Re: [PATCH sched/urgent] sched/deadline: Fix stale rq->dl_server after a server pick returns RETRY_TASK
  2026-10-30 22:18 [PATCH sched/urgent] sched/deadline: Fix stale rq->dl_server after a server pick returns RETRY_TASK Tejun Heo
  2026-10-30 23:56 ` Tejun Heo
@ 2026-10-31  1:19 ` Tejun Heo
  2026-10-31 22:09   ` Tejun Heo
  1 sibling, 1 reply; 4+ messages in thread
From: Tejun Heo @ 2026-10-31  1:19 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Juri Lelli, Vincent Guittot
  Cc: Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
	Valentin Schneider, K Prateek Nayak, linux-kernel

Hello,

This is what I should have sent: a bug report with a reproducer, not the
patch.

__pick_task_dl() records the server in rq->dl_server on RETRY_TASK too, and
__pick_next_task() clears it only above its restart label, so the restarted
pick hands the stale server to whatever task it takes. This became reachable
with b3a2dfa8b42e ("sched/fair: Add newidle balance to pick_task_fair()"),
which makes fair_server_pick_task() return RETRY_TASK when a higher-class
task was enqueued while sched_balance_newidle() had the rq lock dropped.

On mainline abf8c33470f7 in an 8-CPU VM, the reproducer below (60s, 500us
delay) hands the fair server to RT tasks:

  stale rq->dl_server handed to stress-ng-switc/1517 (rt_sched_class)

Six runs counted 8, 44, 0, 7, 24 and 22 hand-offs. No runtime effect of the
stale pointer was observed.

The debug patch widens the newidle lock drop inside a dl server's pick by
fair.dbg_newidle_delay_us and reports the hand-offs. The script needs
stress-ng and root.

  --- a/kernel/sched/core.c
  +++ b/kernel/sched/core.c
  @@ -6240,6 +6240,12 @@ restart:
   		if (unlikely(p == RETRY_TASK))
   			goto restart;
   		if (p) {
  +			static DEFINE_RATELIMIT_STATE(dbg_rs, 5 * HZ, 10);
  +
  +			/* REPRO: only a dl server pick in this pass sets rq->dl_server */
  +			if (rq->dl_server && class != &dl_sched_class && __ratelimit(&dbg_rs))
  +				printk_deferred(KERN_WARNING "stale rq->dl_server handed to %s/%d (%ps)\n",
  +						p->comm, p->pid, class);
   			put_prev_set_next_task(rq, rq->donor, p);
   			return p;
   		}
  --- a/kernel/sched/deadline.c
  +++ b/kernel/sched/deadline.c
  @@ -2813,6 +2813,9 @@ static struct sched_dl_entity *pick_next
   	return __node_2_dle(left);
   }
   
  +/* REPRO: lets sched_balance_newidle() widen its lock drop in a server pick */
  +DEFINE_PER_CPU(bool, dbg_in_dl_server_pick);
  +
   /*
    * __pick_next_task_dl - Helper to pick the next -deadline task to run.
    * @rq: The runqueue to pick the next task from.
  @@ -2831,7 +2834,9 @@ again:
   	WARN_ON_ONCE(!dl_se);
   
   	if (dl_server(dl_se)) {
  +		this_cpu_write(dbg_in_dl_server_pick, true);	/* REPRO */
   		p = dl_se->server_pick_task(dl_se, rf);
  +		this_cpu_write(dbg_in_dl_server_pick, false);	/* REPRO */
   		if (!p) {
   			dl_server_stop(dl_se);
   			goto again;
  --- a/kernel/sched/fair.c
  +++ b/kernel/sched/fair.c
  @@ -21,6 +21,7 @@
    *  Copyright (C) 2007 Red Hat, Inc., Peter Zijlstra
    */
   #include <linux/energy_model.h>
  +#include <linux/delay.h>	/* REPRO */
   #include <linux/mmap_lock.h>
   #include <linux/jiffies.h>
   #include <linux/math.h>
  @@ -14849,6 +14850,11 @@ static inline bool nohz_idle_balance(str
   static inline void nohz_newidle_balance(struct rq *this_rq) { }
   #endif /* !CONFIG_NO_HZ_COMMON */
   
  +/* REPRO: fair.dbg_newidle_delay_us, see __pick_task_dl() */
  +DECLARE_PER_CPU(bool, dbg_in_dl_server_pick);
  +static unsigned int dbg_newidle_delay_us;
  +module_param(dbg_newidle_delay_us, uint, 0644);
  +
   /*
    * sched_balance_newidle is called by schedule() if this_cpu is about to become
    * idle. Attempts to pull tasks from other CPUs.
  @@ -14968,6 +14974,10 @@ static int sched_balance_newidle(struct
   			break;
   	}
   
  +	/* REPRO: widen the drop inside a dl server's pick, after the cost update */
  +	if (dbg_newidle_delay_us && this_cpu_read(dbg_in_dl_server_pick))
  +		udelay(dbg_newidle_delay_us);
  +
   	raw_spin_rq_lock(this_rq);
   
   	if (curr_cost > this_rq->max_idle_balance_cost)

  #!/bin/bash
  #
  # Reproducer for a stale rq->dl_server after a dl server's pick returns
  # RETRY_TASK. Needs a kernel with repro.patch applied, stress-ng and root.
  #
  # RT FIFO hogs starve the fair tasks, so the fair server runs them. The fair
  # tasks sleep often, so the server's pick finds no fair task and runs
  # sched_balance_newidle(), which drops the rq lock. An RT task woken from
  # another CPU in that window makes the pick return RETRY_TASK, and the
  # restarted pick hands the stale server to the RT task.
  #
  # usage: repro.sh [DURATION_S] [DELAY_US]
  
  DURATION=${1:-60}
  DELAY_US=${2:-500}
  KNOB=/sys/module/fair/parameters/dbg_newidle_delay_us
  N=$(nproc)
  
  [ -w $KNOB ] || { echo "$KNOB missing, is repro.patch applied?"; exit 1; }
  dmesg -C
  echo "$DELAY_US" > $KNOB
  
  # RT hogs on every CPU, RT pipe ping-pong across CPUs, fair tasks at 20% load
  stress-ng --cpu "$N" --sched fifo --sched-prio 10 --timeout "${DURATION}s" &> /dev/null &
  stress-ng --switch "$N" --sched fifo --sched-prio 50 --timeout "${DURATION}s" &> /dev/null &
  stress-ng --cpu $((N * 2)) --cpu-load 20 --timeout "${DURATION}s" &> /dev/null &
  stress-ng --switch "$N" --timeout "${DURATION}s" &> /dev/null &
  wait
  echo 0 > $KNOB
  
  dmesg | grep -E 'stale rq->dl_server|__pick_next_task: [0-9]+ callbacks suppressed'
  printed=$(dmesg | grep -c 'stale rq->dl_server')
  suppressed=$(dmesg | sed -n 's/.*__pick_next_task: \([0-9]*\) callbacks suppressed.*/\1/p' |
  	     awk '{ s += $1 } END { print s + 0 }')
  echo "stale rq->dl_server hand-offs: $((printed + suppressed))"

Thanks.

--
tejun

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

* Re: [PATCH sched/urgent] sched/deadline: Fix stale rq->dl_server after a server pick returns RETRY_TASK
  2026-10-31  1:19 ` Tejun Heo
@ 2026-10-31 22:09   ` Tejun Heo
  0 siblings, 0 replies; 4+ messages in thread
From: Tejun Heo @ 2026-10-31 22:09 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Juri Lelli, Vincent Guittot
  Cc: Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
	Valentin Schneider, K Prateek Nayak, linux-kernel

Hello,

The report covered only the non-core pick path, __pick_next_task(). The
core-scheduling pick_next_task() has three more ways of leaving
rq->dl_server wrong. All three reproduce on mainline d89e1d186ec9 in an SMT
VM with the fair server.

1. In the core-wide selection, the cookie pass replaces this CPU's
   first-pass pick, through sched_core_find() or forced idle, without
   clearing the rq->dl_server that a server pick set, so
   put_prev_set_next_task() hands the server to the replacement. This dates
   from bd9bbc96e835 ("sched: Rework dl_server") and reproduces reliably.

2. The cookie pass also clears rq_i->core_dl_server for every sibling, so a
   sibling whose first-pass server pick survived the cookie pass runs it
   from the fast path without its server. Same origin. It reproduces
   reliably.

3. Core scheduling gets enabled while a non-core __pick_next_task() has the
   rq lock dropped in sched_balance_newidle(). core_pick_in_flight counts
   only core-path picks, so __sched_core_flip() doesn't wait for it, and a
   sibling's core-wide selection then runs pick_task() on this rq and sets
   rq->dl_server, which the non-core pick hands to the task the fair class
   picked. It reproduces only rarely, and needs the newidle window widened
   by the patch's 2ms delay and a cookie toggled in a loop. It also happens
   with the RETRY_TASK restart fixed.

In a debug kernel, recomputing the core-wide selection's first-pass max with
the servers the picks actually went through changes the cross-sibling
priority choice and the chosen cookie in some selections; that is a
recomputation, not an observed scheduling outcome.

The scripts need an SMT topology, stress-ng, coresched from util-linux and
root, and the third also chrt.

The case 1 patch reports the hand-offs.

  --- a/kernel/sched/core.c
  +++ b/kernel/sched/core.c
  @@ -6291,6 +6291,10 @@ extern void task_vruntime_update(struct
   
   static void queue_core_balance(struct rq *rq);
   
  +/* REPRO: core.dbg_core_1 counts the hand-offs */
  +static atomic_t dbg_core_1;
  +module_param_named(dbg_core_1, dbg_core_1.counter, int, 0444);
  +
   static struct task_struct *
   pick_next_task(struct rq *rq, struct rq_flags *rf)
   	__must_hold(__rq_lockp(rq))
  @@ -6303,6 +6307,7 @@ pick_next_task(struct rq *rq, struct rq_
   	int i, cpu, occ = 0;
   	struct rq *rq_i;
   	bool need_sync = false;
  +	struct task_struct *pass1 = NULL;	/* REPRO */
   
   	if (!sched_core_enabled(rq))
   		return __pick_next_task(rq, rf);
  @@ -6434,6 +6439,8 @@ restart:
   
   		rq_i->core_pick = p;
   		rq_i->core_dl_server = rq_i->dl_server;
  +		if (i == cpu)
  +			pass1 = p;	/* REPRO */
   
   		if (!max || prio_less(max, p, fi_before))
   			max = p;
  @@ -6482,6 +6489,17 @@ restart:
   	/* Something should have been selected for current CPU */
   	WARN_ON_ONCE(!next);
   
  +	/* REPRO: pass 2 replaced the pass 1 pick, so rq->dl_server is stale */
  +	if (next != pass1 && rq->dl_server) {
  +		static DEFINE_RATELIMIT_STATE(dbg_rs, 5 * HZ, 10);
  +
  +		atomic_inc(&dbg_core_1);
  +		if (__ratelimit(&dbg_rs))
  +			printk_deferred(KERN_WARNING "core pick: stale rq->dl_server handed to %s/%d (%ps), pass 1 picked %s/%d\n",
  +					next->comm, next->pid, next->sched_class,
  +					pass1->comm, pass1->pid);
  +	}
  +
   	/*
   	 * Reschedule siblings
   	 *

  #!/bin/bash
  #
  # Reproducer for core scheduling handing the server of a CPU's pass 1 pick to
  # the task pass 2 replaced it with. Needs a kernel with the case 1 debug patch, an SMT
  # topology, stress-ng, coresched and root.
  #
  # RT FIFO hogs starve the fair tasks, so the fair server runs them. Each load
  # runs under its own core scheduling cookie. When a CPU's fair server picks a
  # fair task while its sibling picks an RT task, the RT task's cookie wins and
  # pass 2 replaces the fair task with a task of that cookie or with idle.
  #
  # usage: repro-1.sh [DURATION_S]
  
  DURATION=${1:-60}
  N=$(nproc)
  
  dmesg -C
  coresched new -- stress-ng --cpu "$N" --sched fifo --sched-prio 10 --timeout "${DURATION}s" &> /dev/null &
  coresched new -- stress-ng --switch "$N" --sched fifo --sched-prio 50 --timeout "${DURATION}s" &> /dev/null &
  coresched new -- stress-ng --cpu $((N * 2)) --cpu-load 20 --timeout "${DURATION}s" &> /dev/null &
  coresched new -- stress-ng --switch "$N" --timeout "${DURATION}s" &> /dev/null &
  wait
  
  dmesg | grep 'core pick: stale rq->dl_server'
  echo "stale rq->dl_server hand-offs: $(cat /sys/module/core/parameters/dbg_core_1)"

The case 2 patch reports the fast-path picks that run without their server.

  --- a/kernel/sched/core.c
  +++ b/kernel/sched/core.c
  @@ -6291,6 +6291,12 @@ extern void task_vruntime_update(struct
   
   static void queue_core_balance(struct rq *rq);
   
  +/* REPRO: each CPU's pass 1 pick and server. core.dbg_core_2 counts drops */
  +static DEFINE_PER_CPU(struct task_struct *, dbg_pass1_pick);
  +static DEFINE_PER_CPU(struct sched_dl_entity *, dbg_pass1_dls);
  +static atomic_t dbg_core_2;
  +module_param_named(dbg_core_2, dbg_core_2.counter, int, 0444);
  +
   static struct task_struct *
   pick_next_task(struct rq *rq, struct rq_flags *rf)
   	__must_hold(__rq_lockp(rq))
  @@ -6335,6 +6341,16 @@ pick_next_task(struct rq *rq, struct rq_
   	if (rq->core->core_pick_seq == rq->core->core_task_seq &&
   	    rq->core_pick) {
   		next = rq->core_pick;
  +		/* REPRO: pass 2 dropped the server that picked @next */
  +		if (next == __this_cpu_read(dbg_pass1_pick) &&
  +		    __this_cpu_read(dbg_pass1_dls) && !rq->core_dl_server) {
  +			static DEFINE_RATELIMIT_STATE(dbg_rs, 5 * HZ, 10);
  +
  +			atomic_inc(&dbg_core_2);
  +			if (__ratelimit(&dbg_rs))
  +				printk_deferred(KERN_WARNING "core pick: rq->dl_server dropped for %s/%d (%ps), pass 1 picked it through a server\n",
  +						next->comm, next->pid, next->sched_class);
  +		}
   		rq->dl_server = rq->core_dl_server;
   		rq->core_pick = NULL;
   		rq->core_dl_server = NULL;
  @@ -6434,6 +6450,8 @@ restart:
   
   		rq_i->core_pick = p;
   		rq_i->core_dl_server = rq_i->dl_server;
  +		per_cpu(dbg_pass1_pick, i) = p;			/* REPRO */
  +		per_cpu(dbg_pass1_dls, i) = rq_i->dl_server;	/* REPRO */
   
   		if (!max || prio_less(max, p, fi_before))
   			max = p;

  #!/bin/bash
  #
  # Reproducer for core scheduling dropping the server of a sibling's pass 1
  # pick that pass 2 kept. Needs a kernel with the case 2 debug patch, an SMT topology,
  # stress-ng, coresched and root.
  #
  # RT FIFO hogs starve the fair tasks, so the fair servers run them. Each load
  # runs under its own core scheduling cookie. When the max is a fair task, a
  # sibling's fair server pick with the same cookie survives pass 2 and runs
  # from the fast path without its server.
  #
  # usage: repro-2.sh [DURATION_S]
  
  DURATION=${1:-60}
  N=$(nproc)
  
  dmesg -C
  coresched new -- stress-ng --cpu "$N" --sched fifo --sched-prio 10 --timeout "${DURATION}s" &> /dev/null &
  coresched new -- stress-ng --switch "$N" --sched fifo --sched-prio 50 --timeout "${DURATION}s" &> /dev/null &
  coresched new -- stress-ng --cpu $((N * 2)) --cpu-load 20 --timeout "${DURATION}s" &> /dev/null &
  coresched new -- stress-ng --switch "$N" --timeout "${DURATION}s" &> /dev/null &
  wait
  
  dmesg | grep 'core pick: rq->dl_server dropped'
  echo "server picks run without the server: $(cat /sys/module/core/parameters/dbg_core_2)"

The case 3 patch widens the newidle lock drop by fair.dbg_newidle_delay_us
once a fair task is queued and reports the hand-offs.

  --- a/kernel/sched/core.c
  +++ b/kernel/sched/core.c
  @@ -6197,6 +6197,30 @@ static void prev_balance(struct rq *rq,
   }
   
   /*
  + * REPRO: per CPU, whether a non-core pick is in progress and the server a
  + * sibling's pick_task() left meanwhile. core.dbg_core_3 counts the hand-offs.
  + */
  +static DEFINE_PER_CPU(bool, dbg_np_pick);
  +static DEFINE_PER_CPU(struct sched_dl_entity *, dbg_sibling_dls);
  +static atomic_t dbg_core_3;
  +module_param_named(dbg_core_3, dbg_core_3.counter, int, 0444);
  +
  +/* REPRO: @p is getting rq->dl_server. @srv_pick means the dl class picked @p */
  +static void dbg_np_handoff(struct rq *rq, struct task_struct *p, bool srv_pick)
  +{
  +	struct sched_dl_entity *dls = __this_cpu_read(dbg_sibling_dls);
  +	static DEFINE_RATELIMIT_STATE(dbg_rs, 5 * HZ, 10);
  +
  +	WRITE_ONCE(*this_cpu_ptr(&dbg_np_pick), false);
  +	if (!srv_pick && dls && rq->dl_server == dls) {
  +		atomic_inc(&dbg_core_3);
  +		if (__ratelimit(&dbg_rs))
  +			printk_deferred(KERN_WARNING "non-core pick: stale rq->dl_server from a sibling's core pick handed to %s/%d (%ps)\n",
  +					p->comm, p->pid, p->sched_class);
  +	}
  +}
  +
  +/*
    * Pick up the highest-prio task:
    */
   static inline struct task_struct *
  @@ -6207,6 +6231,8 @@ __pick_next_task(struct rq *rq, struct r
   	struct task_struct *p;
   
   	rq->dl_server = NULL;
  +	__this_cpu_write(dbg_sibling_dls, NULL);	/* REPRO */
  +	WRITE_ONCE(*this_cpu_ptr(&dbg_np_pick), true);	/* REPRO */
   
   	if (scx_enabled())
   		goto restart;
  @@ -6228,6 +6254,7 @@ __pick_next_task(struct rq *rq, struct r
   		if (!p)
   			p = pick_task_idle(rq, rf);
   
  +		dbg_np_handoff(rq, p, false);	/* REPRO */
   		put_prev_set_next_task(rq, rq->donor, p);
   		return p;
   	}
  @@ -6240,6 +6267,7 @@ restart:
   		if (unlikely(p == RETRY_TASK))
   			goto restart;
   		if (p) {
  +			dbg_np_handoff(rq, p, class == &dl_sched_class);	/* REPRO */
   			put_prev_set_next_task(rq, rq->donor, p);
   			return p;
   		}
  @@ -6280,8 +6308,13 @@ static inline struct task_struct *pick_t
   
   	for_each_active_class(class) {
   		p = class->pick_task(rq, rf);
  -		if (p)
  +		if (p) {
  +			/* REPRO: a sibling picks during @rq's non-core pick */
  +			if (cpu_of(rq) != smp_processor_id() &&
  +			    READ_ONCE(per_cpu(dbg_np_pick, cpu_of(rq))))
  +				per_cpu(dbg_sibling_dls, cpu_of(rq)) = rq->dl_server;
   			return p;
  +		}
   	}
   
   	BUG(); /* The idle class should always have a runnable task. */
  --- a/kernel/sched/fair.c
  +++ b/kernel/sched/fair.c
  @@ -21,6 +21,7 @@
    *  Copyright (C) 2007 Red Hat, Inc., Peter Zijlstra
    */
   #include <linux/energy_model.h>
  +#include <linux/delay.h>	/* REPRO */
   #include <linux/mmap_lock.h>
   #include <linux/jiffies.h>
   #include <linux/math.h>
  @@ -14849,6 +14850,10 @@ static inline bool nohz_idle_balance(str
   static inline void nohz_newidle_balance(struct rq *this_rq) { }
   #endif /* !CONFIG_NO_HZ_COMMON */
   
  +/* REPRO: fair.dbg_newidle_delay_us, see sched_balance_newidle() */
  +static unsigned int dbg_newidle_delay_us;
  +module_param(dbg_newidle_delay_us, uint, 0644);
  +
   /*
    * sched_balance_newidle is called by schedule() if this_cpu is about to become
    * idle. Attempts to pull tasks from other CPUs.
  @@ -14968,6 +14973,10 @@ static int sched_balance_newidle(struct
   			break;
   	}
   
  +	/* REPRO: widen the drop after the cost update with fair tasks queued */
  +	if (dbg_newidle_delay_us && data_race(this_rq->cfs.h_nr_queued))
  +		udelay(dbg_newidle_delay_us);
  +
   	raw_spin_rq_lock(this_rq);
   
   	if (curr_cost > this_rq->max_idle_balance_cost)

  #!/bin/bash
  #
  # Reproducer for a sibling's core-wide pick leaving rq->dl_server set in a
  # non-core pick that released the rq lock before core scheduling got enabled.
  # Needs a kernel with the case 3 debug patch, an SMT topology, stress-ng, coresched
  # and root.
  #
  # RT FIFO hogs on one thread of each core make the fair servers run there.
  # Sleeping fair tasks make the picks pull tasks in newidle balancing, and the
  # knob widens the rq lock release once a fair task is queued. A busy group
  # gets a new cookie and has it cleared in a loop, which enables and disables
  # core scheduling and makes the busy tasks' CPUs run core-wide selections
  # right after each enable.
  #
  # usage: repro-3.sh [DURATION_S] [DELAY_US]
  
  DURATION=${1:-60}
  DELAY_US=${2:-2000}
  KNOB=/sys/module/fair/parameters/dbg_newidle_delay_us
  N=$(nproc)
  
  [ -w $KNOB ] || { echo "$KNOB missing, is the case 3 debug patch applied?"; exit 1; }
  dmesg -C
  echo "$DELAY_US" > $KNOB
  
  stress-ng --cpu $((N / 2)) --taskset even --cpu-load 95 --sched fifo --sched-prio 10 \
  	--timeout "${DURATION}s" &> /dev/null &
  stress-ng --cpu $((N * 2)) --cpu-load 20 --timeout "${DURATION}s" &> /dev/null &
  stress-ng --switch "$N" --timeout "${DURATION}s" &> /dev/null &
  setsid stress-ng --cpu "$N" --cpu-load 50 --timeout "${DURATION}s" &> /dev/null &
  BUSY=$!
  sleep 1
  
  # the toggler runs above the RT hogs, or they starve it
  chrt -f 90 bash -c '
  	end=$((SECONDS + $1))
  	while [ $SECONDS -lt $end ]; do
  		coresched new -t pgid -d $2 || break
  		sleep 0.01
  		coresched copy -s $$ -t pgid -d $2 || break
  		sleep 0.02
  	done
  ' toggler $((DURATION - 2)) $BUSY
  wait
  echo 0 > $KNOB
  
  dmesg | grep 'stale rq->dl_server from a sibling'
  echo "sibling-written rq->dl_server hand-offs: $(cat /sys/module/core/parameters/dbg_core_3)"

Thanks.

--
tejun

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

end of thread, other threads:[~2026-10-31 22:09 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-30 22:18 [PATCH sched/urgent] sched/deadline: Fix stale rq->dl_server after a server pick returns RETRY_TASK Tejun Heo
2026-10-30 23:56 ` Tejun Heo
2026-10-31  1:19 ` Tejun Heo
2026-10-31 22:09   ` Tejun Heo

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®