* [BUG] Apparent lost wakeups
@ 2026-10-12 18:41 Paul E. McKenney
2026-10-13 6:06 ` K Prateek Nayak
2026-10-13 15:24 ` [PATCH] sched/core: Call wakeup_preempt() for queued tasks during class change K Prateek Nayak
0 siblings, 2 replies; 18+ messages in thread
From: Paul E. McKenney @ 2026-10-12 18:41 UTC (permalink / raw)
To: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot
Cc: Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Joel Fernandes,
linux-kernel, kernel-team
The scheduler appears to lose wakeups on both x86 and arm64 systems
when running the rcutorture TREE01 scenario with RCU priority boosting
enabled, but without also enabling testing of RCU priority boosting.
The following pair of commits in my -rcu tree [1] make these two changes:
c7684673c6ca ("EXP rcutorture: Enable RCU_BOOST where supported")
4b08261f7bff ("EXP rcutorture: Disable RCU priority boost testing on non-TREE03 scenarios")
These two changes were motivated by CI systems reporting RCU CPU stall
warnings due to low-priority tasks being preempted for extended periods
of time. The hope is that undisciplined RCU priority boosting would
eliminate these issues. (For an example of a disciplined RCU priority
boosting configuration, see the TREE03 scenario.)
The following command reproduces the lost-wakeup problem, which shows
up as RCU CPU stall warnings caused by rcu_torture_reader() kthreads:
tools/testing/selftests/rcutorture/bin/kvm.sh --allcpus --duration 20m --configs '19*TREE01' --bootargs "nohz=off rcutorture.onoff_interval=0 rcutorture.nocbs_nthreads=0 rcutorture.shuffle_interval=0" --trust-make
Replace the "19" in the "19*TREE01" above with the number of CPUs on your
system divided by 8, rounded down.
This issue occurs on both x86 and arm64 systems.
This wakeup failure occurs in other scenarios, but more rarely. The thing
that makes the above reproducer do better (or worse, depending on your
perspective) is that it is providing only eight CPUs for each guest OS's
17 vCPUs. This could be considered abusive, but if so, it is abuse that
provides orders-of-magnitude acceleration of a real failure that also
happens without vCPU overcommit.
The following pair of commits add diagnostics that show that the stalled
kthread is consistently preempted on an idle CPU, which is a perfectly
reasonable transient state (for example, during delivery of a resched
IPI), but not a state that should persist for many tens of seconds:
49be2ba9f0fa ("rcu: Add running and boosted indications to RCU task stall dump")
e3bbe32c1fd9 ("EXP rcu: Print CPU that preempted stalled reader wants to run on")
It appears that the cause of this issue is the wakeup racing with the
target CPU entering the idle loop. The AI-slop patch shown below does
a recheck of flags to detect this condition and exit the idle loop when
detected. Note that this is *not* a complete fix in the general case
because the wakeup might happen just after this check, and to its credit
the AI agrees that this is not a complete fix. However, this "fix"
does appear to prevent (or at least severely decrease the probability
of) the problem for the above reproducer because of the nohz=off kernel
boot parameter.
This patch also splats when the recheck detects a racing wakeup, which
demonstrates that this patch is actually doing something.
An alternative and (apparently) equally effective workaround,
involving resched_cpu() executed by the RCU grace-period kthread's
force-quiescent-state processing, may be found in my -rcu tree:
34bace6b7fab ("EXP rcu: Send resched_cpu() to idle CPUs having preempted RCU readers")
So why does is it necessary to disable rcutorture's testing of RCU
priority boosting to reproduce this lost-waitup issue? Because enabling
this testing means that there are per-CPU kthreads that wake up once
per second, which forces each CPU to notice any runnable tasks.
As always, please let me know how I can help with further debugging.
[1] https://git.kernel.org/pub/scm/linux/kernel/git/paulmck/linux-rcu.git
Assisted-by: LLM
Not-signed-off-by: Paul E. McKenney <paulmck@kernel.org>
diff --git a/kernel/sched/idle.c b/kernel/sched/idle.c
index eb73b65ce6c47..90f2dfc67e2d0 100644
--- a/kernel/sched/idle.c
+++ b/kernel/sched/idle.c
@@ -279,6 +279,7 @@ static void do_idle(void)
{
int cpu = smp_processor_id();
bool got_tick = false;
+ struct rq *rq;
if (cpu_is_offline(cpu)) {
local_irq_disable();
@@ -339,6 +340,24 @@ static void do_idle(void)
*/
local_irq_disable();
+ /*
+ * Lost-kick safety net: never sleep with genuinely runnable
+ * work queued. Exclude delayed-dequeue tasks, which are
+ * counted in nr_running but not pickable (as in
+ * wake_affine_idle()). Lockless reads are fine: a spurious
+ * hit costs one extra schedule pass, a miss is re-checked
+ * on the next tick/interrupt.
+ */
+ rq = this_rq();
+ if (READ_ONCE(rq->nr_running) >
+ READ_ONCE(rq->cfs.h_nr_queued) - READ_ONCE(rq->cfs.h_nr_runnable)) {
+ WARN_ONCE(1, "CPU %d entering idle with %u runnable tasks queued\n",
+ cpu, READ_ONCE(rq->nr_running));
+ set_tsk_need_resched(current);
+ local_irq_enable();
+ continue;
+ }
+
arch_cpu_idle_enter();
rcu_nocb_flush_deferred_wakeup();
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [BUG] Apparent lost wakeups
2026-10-12 18:41 [BUG] Apparent lost wakeups Paul E. McKenney
@ 2026-10-13 6:06 ` K Prateek Nayak
2026-10-13 6:46 ` K Prateek Nayak
2026-10-13 15:24 ` [PATCH] sched/core: Call wakeup_preempt() for queued tasks during class change K Prateek Nayak
1 sibling, 1 reply; 18+ messages in thread
From: K Prateek Nayak @ 2026-10-13 6:06 UTC (permalink / raw)
To: paulmck, Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot
Cc: Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, Joel Fernandes, linux-kernel, kernel-team
Hello Paul,
On 10/13/2026 12:11 AM, Paul E. McKenney wrote:
> The scheduler appears to lose wakeups on both x86 and arm64 systems
> when running the rcutorture TREE01 scenario with RCU priority boosting
> enabled, but without also enabling testing of RCU priority boosting.
> The following pair of commits in my -rcu tree [1] make these two changes:
>
> c7684673c6ca ("EXP rcutorture: Enable RCU_BOOST where supported")
> 4b08261f7bff ("EXP rcutorture: Disable RCU priority boost testing on non-TREE03 scenarios")
>
> These two changes were motivated by CI systems reporting RCU CPU stall
> warnings due to low-priority tasks being preempted for extended periods
> of time. The hope is that undisciplined RCU priority boosting would
> eliminate these issues. (For an example of a disciplined RCU priority
> boosting configuration, see the TREE03 scenario.)
>
> The following command reproduces the lost-wakeup problem, which shows
> up as RCU CPU stall warnings caused by rcu_torture_reader() kthreads:
>
> tools/testing/selftests/rcutorture/bin/kvm.sh --allcpus --duration 20m --configs '19*TREE01' --bootargs "nohz=off rcutorture.onoff_interval=0 rcutorture.nocbs_nthreads=0 rcutorture.shuffle_interval=0" --trust-make
tl;dr
I could hit that WARN_ON() from your debug patch with the above test
even under rq_lock. Seemed like a missing resched_curr() but it
turned out to also be related to rq->next_class interaction.
I haven't been able to hit the warnings with the below diff. Could
you please try this:
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 0f63c30b4324..f2f8af3cdfb5 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -11239,6 +11245,11 @@ void sched_change_end(struct sched_change_ctx *ctx)
if (p->sched_class->switched_to)
p->sched_class->switched_to(rq, p);
+ if (ctx->queued && sched_class_above(p->sched_class, rq->next_class)) {
+ rq->next_class = p->sched_class;
+ resched_curr(rq);
+ }
+
if (ctx->running) {
/*
* If this was a class promotion; let the old class
---
Larger explanation
==================
I added more debug data and I see what happens at time of last wakeup:
Last wakeup comm: rcu_torture_rea
rq->next_class: 000000008d901c4b
p->sched_class: 000000008d901c4b
next_class: idle?(0) fair?(1) rt?(0) dl?(0) stop?(0)
p->sched class: idle?(0) fair?(1) rt?(0) dl?(0) stop?(0)
need_resched_set_after_wakeup: 1
And then at the time when warning hits, we have:
h_nr_runnable: 0
h_nr_queued: 0
rt_nr_running: 1
rr_nr_running: 0
dl_nr_running: 0
Looks like the task that is enqueued is RT but the enqueue context saw
it as a fair task which pointed to __sched_setscheduler().
When pick_task_fair() drops rq_lock() for newidle balance, it is
possible to queue a task and switch it to a higher class before the
CPU doing newidle balance grabs the rq_lock() again.
It then sees rq->next_class is still fair and only checks
"rq->cfs.h_nr_queued" which is now 0 because that task was moved to
RT. The task was not running so we neither upgraded the next_class,
nor did we do a resched.
sched_balance_newidle() returns 0 and since we are already at
pick_task_fair(), we think nothing has queued above us and bail out.
The above diff fixes rq->next_class and calls a resched_curr()
when the task in upgraded but not yet running.
Lightly tested with TREE01 which passes without complains after
adding the above diff. Your mileage may vary :-)
--
Thanks and Regards,
Prateek
=========================
Artifact: Full debug diff
=========================
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 0f63c30b4324..f2f8af3cdfb5 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -2305,6 +2305,10 @@ void wakeup_preempt(struct rq *rq, struct task_struct *p, int flags)
{
struct task_struct *donor = rq->donor;
+ rq->last_comm = p->comm;
+ rq->last_class = rq->next_class;
+ rq->wakeup_class = p->sched_class;
+
if (p->sched_class == rq->next_class) {
rq->next_class->wakeup_preempt(rq, p, flags);
@@ -2314,6 +2318,8 @@ void wakeup_preempt(struct rq *rq, struct task_struct *p, int flags)
rq->next_class = p->sched_class;
}
+ rq->nr_set_after_wakeup = test_tsk_need_resched(rq->curr);
+
/*
* A queue event has occurred, and we're going to schedule. In
* this case, we can save a useless back to back clock update.
@@ -11239,6 +11245,11 @@ void sched_change_end(struct sched_change_ctx *ctx)
if (p->sched_class->switched_to)
p->sched_class->switched_to(rq, p);
+ if (ctx->queued && sched_class_above(p->sched_class, rq->next_class)) {
+ rq->next_class = p->sched_class;
+ resched_curr(rq);
+ }
+
if (ctx->running) {
/*
* If this was a class promotion; let the old class
diff --git a/kernel/sched/idle.c b/kernel/sched/idle.c
index 76f3c84ca684..7a0f2f701d56 100644
--- a/kernel/sched/idle.c
+++ b/kernel/sched/idle.c
@@ -279,6 +279,7 @@ static void do_idle(void)
{
int cpu = smp_processor_id();
bool got_tick = false;
+ struct rq *rq;
if (cpu_is_offline(cpu)) {
local_irq_disable();
@@ -306,6 +307,7 @@ static void do_idle(void)
tick_nohz_idle_enter();
while (!need_resched()) {
+ bool redo = false;
/*
* Interrupts shouldn't be re-enabled from that point on until
@@ -339,6 +341,69 @@ static void do_idle(void)
*/
local_irq_disable();
+ /*
+ * Lost-kick safety net: never sleep with genuinely runnable
+ * work queued. Exclude delayed-dequeue tasks, which are
+ * counted in nr_running but not pickable (as in
+ * wake_affine_idle()). Lockless reads are fine: a spurious
+ * hit costs one extra schedule pass, a miss is re-checked
+ * on the next tick/interrupt.
+ */
+ rq = this_rq();
+
+ scoped_guard (rq_lock, rq) {
+ struct rq_flags *rf = &scope.rf;
+
+ if ((READ_ONCE(rq->nr_running) >
+ READ_ONCE(rq->cfs.h_nr_queued) - READ_ONCE(rq->cfs.h_nr_runnable)) &&
+ !need_resched()) {
+ WARN_ONCE(1, "CPU %d entering idle with %u runnable tasks queued\n",
+ cpu, READ_ONCE(rq->nr_running));
+
+ pr_warn("Last comm: %s\n", rq->last_comm);
+ pr_warn("Last class: %p\n", rq->last_class);
+ pr_warn("Wakeup class: %p\n", rq->wakeup_class);
+
+ pr_warn("Last class: idle?(%d) fair?(%d) rt?(%d) dl?(%d) stop?(%d)\n",
+ rq->last_class == &idle_sched_class,
+ rq->last_class == &fair_sched_class,
+ rq->last_class == &rt_sched_class,
+ rq->last_class == &dl_sched_class,
+ rq->last_class == &stop_sched_class);
+
+ pr_warn("Wake class: idle?(%d) fair?(%d) rt?(%d) dl?(%d) stop?(%d)\n",
+ rq->wakeup_class == &idle_sched_class,
+ rq->wakeup_class == &fair_sched_class,
+ rq->wakeup_class == &rt_sched_class,
+ rq->wakeup_class == &dl_sched_class,
+ rq->wakeup_class == &stop_sched_class);
+
+ pr_warn("nr_set_after_wakeup: %d\n", rq->nr_set_after_wakeup);
+ pr_warn("h_nr_runnable: %u\n", rq->cfs.h_nr_runnable);
+ pr_warn("h_nr_queued: %u\n", rq->cfs.h_nr_queued);
+
+ pr_warn("rt_nr_running: %u\n", rq->rt.rt_nr_running);
+ pr_warn("rr_nr_running: %u\n", rq->rt.rr_nr_running);
+ pr_warn("dl_nr_running: %u\n", rq->dl.dl_nr_running);
+
+
+ if (rq->cfs.h_nr_queued) {
+ update_rq_clock(rq);
+ pr_warn("Pick on fair: %p\n", fair_sched_class.pick_task(rq, rf));
+ }
+
+ // set_tsk_need_resched(current);
+ // redo = true;
+ break;
+ }
+ }
+
+ if (redo) {
+ local_irq_enable();
+ continue;
+ }
+
+
arch_cpu_idle_enter();
rcu_nocb_flush_deferred_wakeup();
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index 7d2ec527b8a2..7e685cc51e43 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -1253,6 +1253,11 @@ struct rq {
unsigned long next_balance;
struct mm_struct *prev_mm;
+ char *last_comm;
+ const struct sched_class *last_class;
+ const struct sched_class *wakeup_class;
+ int nr_set_after_wakeup;
+
/*
* The following fields of clock data are frequently referenced
* and updated together, and should go on their own cache line.
---
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [BUG] Apparent lost wakeups
2026-10-13 6:06 ` K Prateek Nayak
@ 2026-10-13 6:46 ` K Prateek Nayak
2026-10-13 7:58 ` Vincent Guittot
2026-10-13 10:06 ` Peter Zijlstra
0 siblings, 2 replies; 18+ messages in thread
From: K Prateek Nayak @ 2026-10-13 6:46 UTC (permalink / raw)
To: paulmck, Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot
Cc: Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, Joel Fernandes, linux-kernel, kernel-team
On 10/13/2026 11:36 AM, K Prateek Nayak wrote:
>> The following command reproduces the lost-wakeup problem, which shows
>> up as RCU CPU stall warnings caused by rcu_torture_reader() kthreads:
>>
>> tools/testing/selftests/rcutorture/bin/kvm.sh --allcpus --duration 20m --configs '19*TREE01' --bootargs "nohz=off rcutorture.onoff_interval=0 rcutorture.nocbs_nthreads=0 rcutorture.shuffle_interval=0" --trust-make
>
> tl;dr
>
> I could hit that WARN_ON() from your debug patch with the above test
> even under rq_lock. Seemed like a missing resched_curr() but it
> turned out to also be related to rq->next_class interaction.
>
> I haven't been able to hit the warnings with the below diff. Could
> you please try this:
>
> diff --git a/kernel/sched/core.c b/kernel/sched/core.c
> index 0f63c30b4324..f2f8af3cdfb5 100644
> --- a/kernel/sched/core.c
> +++ b/kernel/sched/core.c
> @@ -11239,6 +11245,11 @@ void sched_change_end(struct sched_change_ctx *ctx)
> if (p->sched_class->switched_to)
> p->sched_class->switched_to(rq, p);
>
> + if (ctx->queued && sched_class_above(p->sched_class, rq->next_class)) {
> + rq->next_class = p->sched_class;
> + resched_curr(rq);
> + }
> +
> if (ctx->running) {
> /*
> * If this was a class promotion; let the old class
> ---
And now that I've finally woken up just like that task, it can be
simplified to:
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 0f63c30b4324..b37c771c41ad 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -11256,6 +11262,9 @@ void sched_change_end(struct sched_change_ctx *ctx)
*/
if (sched_class_above(ctx->class, p->sched_class))
resched_curr(rq);
+ } else if (ctx->queued) {
+ /* Check if we need can preempt the current task. */
+ wakeup_preempt(rq, p, 0);
}
} else {
p->sched_class->prio_changed(rq, p, ctx->prio);
---
Peter, what do you think?
> Lightly tested with TREE01 which passes without complains after
> adding the above diff. Your mileage may vary :-)
That disclaimer still applies.
P.S. This is the output I see with the fix on my 512 CPUs machine
and 64*TREE01. I hope this is the expected stuff:
TREE01 ------- 34879 GPs (29.0658/s) [rcu: g238089 f0x0 total-gps=59806]
TREE01.10 ------- 33788 GPs (28.1567/s) [rcu: g234153 f0x0 total-gps=58824]
TREE01.11 ------- 35987 GPs (29.9892/s) [rcu: g245677 f0x0 total-gps=61702]
TREE01.12 ------- 34864 GPs (29.0533/s) [rcu: g239025 f0x0 total-gps=60039]
TREE01.13 ------- 34143 GPs (28.4525/s) [rcu: g234145 f0x0 total-gps=58821]
TREE01.14 ------- 34603 GPs (28.8358/s) [rcu: g238249 f0x0 total-gps=59848]
TREE01.15 ------- 34654 GPs (28.8783/s) [rcu: g236633 f0x0 total-gps=59444]
TREE01.16 ------- 34714 GPs (28.9283/s) [rcu: g236617 f0x0 total-gps=59438]
TREE01.17 ------- 33838 GPs (28.1983/s) [rcu: g233041 f0x0 total-gps=58545]
TREE01.18 ------- 34119 GPs (28.4325/s) [rcu: g232869 f0x0 total-gps=58502]
TREE01.19 ------- 34087 GPs (28.4058/s) [rcu: g234413 f0x0 total-gps=58888]
TREE01.2 ------- 33669 GPs (28.0575/s) [rcu: g231649 f0x0 total-gps=58198]
TREE01.20 ------- 33240 GPs (27.7/s) [rcu: g228757 f0x0 total-gps=57475]
TREE01.21 ------- 33656 GPs (28.0467/s) [rcu: g230577 f0x0 total-gps=57929]
TREE01.22 ------- 34505 GPs (28.7542/s) [rcu: g233817 f0x0 total-gps=58741]
TREE01.23 ------- 35031 GPs (29.1925/s) [rcu: g239141 f0x0 total-gps=60071]
TREE01.24 ------- 34771 GPs (28.9758/s) [rcu: g237589 f0x0 total-gps=59683]
TREE01.25 ------- 33751 GPs (28.1258/s) [rcu: g229969 f0x0 total-gps=57778]
TREE01.26 ------- 35695 GPs (29.7458/s) [rcu: g244333 f0x0 total-gps=61369]
TREE01.27 ------- 33001 GPs (27.5008/s) [rcu: g227229 f0x0 total-gps=57093]
TREE01.28 ------- 33399 GPs (27.8325/s) [rcu: g230181 f0x0 total-gps=57830]
TREE01.29 ------- 34602 GPs (28.835/s) [rcu: g235361 f0x0 total-gps=59125]
TREE01.3 ------- 35795 GPs (29.8292/s) [rcu: g243365 f0x0 total-gps=61126]
TREE01.30 ------- 34129 GPs (28.4408/s) [rcu: g233673 f0x0 total-gps=58704]
TREE01.31 ------- 34713 GPs (28.9275/s) [rcu: g236941 f0x0 total-gps=59521]
TREE01.32 ------- 33953 GPs (28.2942/s) [rcu: g233017 f0x0 total-gps=58540]
TREE01.33 ------- 34353 GPs (28.6275/s) [rcu: g234397 f0x0 total-gps=58885]
TREE01.34 ------- 34836 GPs (29.03/s) [rcu: g238653 f0x0 total-gps=59948]
TREE01.35 ------- 33714 GPs (28.095/s) [rcu: g232241 f0x0 total-gps=58344]
TREE01.36 ------- 34704 GPs (28.92/s) [rcu: g236349 f0x0 total-gps=59373]
TREE01.37 ------- 34205 GPs (28.5042/s) [rcu: g232549 f0x0 total-gps=58424]
TREE01.38 ------- 33833 GPs (28.1942/s) [rcu: g232017 f0x0 total-gps=58292]
TREE01.39 ------- 34001 GPs (28.3342/s) [rcu: g233089 f0x0 total-gps=58558]
TREE01.4 ------- 35768 GPs (29.8067/s) [rcu: g243141 f0x0 total-gps=61069]
TREE01.40 ------- 35209 GPs (29.3408/s) [rcu: g239941 f0x0 total-gps=60272]
TREE01.41 ------- 34950 GPs (29.125/s) [rcu: g237825 f0x0 total-gps=59743]
TREE01.42 ------- 34396 GPs (28.6633/s) [rcu: g234305 f0x0 total-gps=58863]
TREE01.43 ------- 33162 GPs (27.635/s) [rcu: g227049 f0x0 total-gps=57046]
TREE01.44 ------- 34965 GPs (29.1375/s) [rcu: g238837 f0x0 total-gps=59994]
TREE01.45 ------- 33531 GPs (27.9425/s) [rcu: g229529 f0x0 total-gps=57665]
TREE01.46 ------- 34167 GPs (28.4725/s) [rcu: g235829 f0x0 total-gps=59242]
TREE01.47 ------- 34538 GPs (28.7817/s) [rcu: g236357 f0x0 total-gps=59375]
TREE01.48 ------- 34491 GPs (28.7425/s) [rcu: g235029 f0x0 total-gps=59042]
TREE01.49 ------- 36278 GPs (30.2317/s) [rcu: g245997 f0x0 total-gps=61784]
TREE01.5 ------- 35415 GPs (29.5125/s) [rcu: g240741 f0x0 total-gps=60471]
TREE01.50 ------- 34928 GPs (29.1067/s) [rcu: g236977 f0x0 total-gps=59529]
TREE01.51 ------- 33065 GPs (27.5542/s) [rcu: g226573 f0x0 total-gps=56929]
TREE01.52 ------- 34111 GPs (28.4258/s) [rcu: g234373 f0x0 total-gps=58878]
TREE01.53 ------- 35627 GPs (29.6892/s) [rcu: g242797 f0x0 total-gps=60985]
TREE01.54 ------- 33155 GPs (27.6292/s) [rcu: g227505 f0x0 total-gps=57160]
TREE01.55 ------- 34386 GPs (28.655/s) [rcu: g233137 f0x0 total-gps=58571]
TREE01.56 ------- 35130 GPs (29.275/s) [rcu: g237817 f0x0 total-gps=59740]
TREE01.57 ------- 34660 GPs (28.8833/s) [rcu: g236409 f0x0 total-gps=59389]
TREE01.58 ------- 34901 GPs (29.0842/s) [rcu: g236937 f0x0 total-gps=59521]
TREE01.59 ------- 34915 GPs (29.0958/s) [rcu: g238425 f0x0 total-gps=59892]
TREE01.6 ------- 35682 GPs (29.735/s) [rcu: g239305 f0x0 total-gps=60112]
TREE01.60 ------- 34643 GPs (28.8692/s) [rcu: g235361 f0x0 total-gps=59126]
TREE01.61 ------- 34343 GPs (28.6192/s) [rcu: g234857 f0x0 total-gps=58999]
TREE01.62 ------- 34592 GPs (28.8267/s) [rcu: g233665 f0x0 total-gps=58703]
TREE01.63 ------- 35315 GPs (29.4292/s) [rcu: g239865 f0x0 total-gps=60250]
TREE01.64 ------- 34793 GPs (28.9942/s) [rcu: g240109 f0x0 total-gps=60311]
TREE01.7 ------- 34171 GPs (28.4758/s) [rcu: g233977 f0x0 total-gps=58779]
TREE01.8 ------- 33806 GPs (28.1717/s) [rcu: g233497 f0x0 total-gps=58659]
TREE01.9 ------- 34083 GPs (28.4025/s) [rcu: g237765 f0x0 total-gps=59727]
--- Done at Tue Oct 13 06:44:58 AM UTC 2026 (22:16.0) exitcode 0
I miss those days where I could just turn around and ask Neeraj
if this was correct at the office!
--
Thanks and Regards,
Prateek
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [BUG] Apparent lost wakeups
2026-10-13 6:46 ` K Prateek Nayak
@ 2026-10-13 7:58 ` Vincent Guittot
2026-10-13 8:30 ` K Prateek Nayak
2026-10-13 10:14 ` Peter Zijlstra
2026-10-13 10:06 ` Peter Zijlstra
1 sibling, 2 replies; 18+ messages in thread
From: Vincent Guittot @ 2026-10-13 7:58 UTC (permalink / raw)
To: K Prateek Nayak
Cc: paulmck, Ingo Molnar, Peter Zijlstra, Juri Lelli,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, Joel Fernandes, linux-kernel, kernel-team
On Tue, 13 Oct 2026 at 08:46, K Prateek Nayak <kprateek.nayak@amd.com> wrote:
>
> On 10/13/2026 11:36 AM, K Prateek Nayak wrote:
> >> The following command reproduces the lost-wakeup problem, which shows
> >> up as RCU CPU stall warnings caused by rcu_torture_reader() kthreads:
> >>
> >> tools/testing/selftests/rcutorture/bin/kvm.sh --allcpus --duration 20m --configs '19*TREE01' --bootargs "nohz=off rcutorture.onoff_interval=0 rcutorture.nocbs_nthreads=0 rcutorture.shuffle_interval=0" --trust-make
> >
> > tl;dr
> >
> > I could hit that WARN_ON() from your debug patch with the above test
> > even under rq_lock. Seemed like a missing resched_curr() but it
> > turned out to also be related to rq->next_class interaction.
> >
> > I haven't been able to hit the warnings with the below diff. Could
> > you please try this:
> >
> > diff --git a/kernel/sched/core.c b/kernel/sched/core.c
> > index 0f63c30b4324..f2f8af3cdfb5 100644
> > --- a/kernel/sched/core.c
> > +++ b/kernel/sched/core.c
> > @@ -11239,6 +11245,11 @@ void sched_change_end(struct sched_change_ctx *ctx)
> > if (p->sched_class->switched_to)
> > p->sched_class->switched_to(rq, p);
> >
> > + if (ctx->queued && sched_class_above(p->sched_class, rq->next_class)) {
> > + rq->next_class = p->sched_class;
> > + resched_curr(rq);
> > + }
> > +
> > if (ctx->running) {
> > /*
> > * If this was a class promotion; let the old class
> > ---
>
> And now that I've finally woken up just like that task, it can be
> simplified to:
>
> diff --git a/kernel/sched/core.c b/kernel/sched/core.c
> index 0f63c30b4324..b37c771c41ad 100644
> --- a/kernel/sched/core.c
> +++ b/kernel/sched/core.c
> @@ -11256,6 +11262,9 @@ void sched_change_end(struct sched_change_ctx *ctx)
if (ctx->running) {
/*
* If this was a class promotion; let the old class
* know it got preempted. Note that none of the
* switch*_from() methods know the new class and none
* of the switch*_to() methods know the old class.
*/
if (sched_class_above(p->sched_class, ctx->class)) {
rq->next_class->wakeup_preempt(rq, p, 0);
rq->next_class = p->sched_class;
}
/*
* If this was a degradation in class; make sure to
* reschedule.
> */
> if (sched_class_above(ctx->class, p->sched_class))
> resched_curr(rq);
> + } else if (ctx->queued) {
> + /* Check if we need can preempt the current task. */
> + wakeup_preempt(rq, p, 0);
Isn't what we have for ctx->running above what we also want in this case ?
Then sched_balance_newidle() will detect it with
/* If a higher prio class was modified, restart the pick */
if (rq_modified_above(this_rq, &fair_sched_class))
pulled_task = -1;
> }
> } else {
> p->sched_class->prio_changed(rq, p, ctx->prio);
> ---
>
> Peter, what do you think?
> > Lightly tested with TREE01 which passes without complains after
> > adding the above diff. Your mileage may vary :-)
>
> That disclaimer still applies.
>
> P.S. This is the output I see with the fix on my 512 CPUs machine
> and 64*TREE01. I hope this is the expected stuff:
>
> TREE01 ------- 34879 GPs (29.0658/s) [rcu: g238089 f0x0 total-gps=59806]
> TREE01.10 ------- 33788 GPs (28.1567/s) [rcu: g234153 f0x0 total-gps=58824]
> TREE01.11 ------- 35987 GPs (29.9892/s) [rcu: g245677 f0x0 total-gps=61702]
> TREE01.12 ------- 34864 GPs (29.0533/s) [rcu: g239025 f0x0 total-gps=60039]
> TREE01.13 ------- 34143 GPs (28.4525/s) [rcu: g234145 f0x0 total-gps=58821]
> TREE01.14 ------- 34603 GPs (28.8358/s) [rcu: g238249 f0x0 total-gps=59848]
> TREE01.15 ------- 34654 GPs (28.8783/s) [rcu: g236633 f0x0 total-gps=59444]
> TREE01.16 ------- 34714 GPs (28.9283/s) [rcu: g236617 f0x0 total-gps=59438]
> TREE01.17 ------- 33838 GPs (28.1983/s) [rcu: g233041 f0x0 total-gps=58545]
> TREE01.18 ------- 34119 GPs (28.4325/s) [rcu: g232869 f0x0 total-gps=58502]
> TREE01.19 ------- 34087 GPs (28.4058/s) [rcu: g234413 f0x0 total-gps=58888]
> TREE01.2 ------- 33669 GPs (28.0575/s) [rcu: g231649 f0x0 total-gps=58198]
> TREE01.20 ------- 33240 GPs (27.7/s) [rcu: g228757 f0x0 total-gps=57475]
> TREE01.21 ------- 33656 GPs (28.0467/s) [rcu: g230577 f0x0 total-gps=57929]
> TREE01.22 ------- 34505 GPs (28.7542/s) [rcu: g233817 f0x0 total-gps=58741]
> TREE01.23 ------- 35031 GPs (29.1925/s) [rcu: g239141 f0x0 total-gps=60071]
> TREE01.24 ------- 34771 GPs (28.9758/s) [rcu: g237589 f0x0 total-gps=59683]
> TREE01.25 ------- 33751 GPs (28.1258/s) [rcu: g229969 f0x0 total-gps=57778]
> TREE01.26 ------- 35695 GPs (29.7458/s) [rcu: g244333 f0x0 total-gps=61369]
> TREE01.27 ------- 33001 GPs (27.5008/s) [rcu: g227229 f0x0 total-gps=57093]
> TREE01.28 ------- 33399 GPs (27.8325/s) [rcu: g230181 f0x0 total-gps=57830]
> TREE01.29 ------- 34602 GPs (28.835/s) [rcu: g235361 f0x0 total-gps=59125]
> TREE01.3 ------- 35795 GPs (29.8292/s) [rcu: g243365 f0x0 total-gps=61126]
> TREE01.30 ------- 34129 GPs (28.4408/s) [rcu: g233673 f0x0 total-gps=58704]
> TREE01.31 ------- 34713 GPs (28.9275/s) [rcu: g236941 f0x0 total-gps=59521]
> TREE01.32 ------- 33953 GPs (28.2942/s) [rcu: g233017 f0x0 total-gps=58540]
> TREE01.33 ------- 34353 GPs (28.6275/s) [rcu: g234397 f0x0 total-gps=58885]
> TREE01.34 ------- 34836 GPs (29.03/s) [rcu: g238653 f0x0 total-gps=59948]
> TREE01.35 ------- 33714 GPs (28.095/s) [rcu: g232241 f0x0 total-gps=58344]
> TREE01.36 ------- 34704 GPs (28.92/s) [rcu: g236349 f0x0 total-gps=59373]
> TREE01.37 ------- 34205 GPs (28.5042/s) [rcu: g232549 f0x0 total-gps=58424]
> TREE01.38 ------- 33833 GPs (28.1942/s) [rcu: g232017 f0x0 total-gps=58292]
> TREE01.39 ------- 34001 GPs (28.3342/s) [rcu: g233089 f0x0 total-gps=58558]
> TREE01.4 ------- 35768 GPs (29.8067/s) [rcu: g243141 f0x0 total-gps=61069]
> TREE01.40 ------- 35209 GPs (29.3408/s) [rcu: g239941 f0x0 total-gps=60272]
> TREE01.41 ------- 34950 GPs (29.125/s) [rcu: g237825 f0x0 total-gps=59743]
> TREE01.42 ------- 34396 GPs (28.6633/s) [rcu: g234305 f0x0 total-gps=58863]
> TREE01.43 ------- 33162 GPs (27.635/s) [rcu: g227049 f0x0 total-gps=57046]
> TREE01.44 ------- 34965 GPs (29.1375/s) [rcu: g238837 f0x0 total-gps=59994]
> TREE01.45 ------- 33531 GPs (27.9425/s) [rcu: g229529 f0x0 total-gps=57665]
> TREE01.46 ------- 34167 GPs (28.4725/s) [rcu: g235829 f0x0 total-gps=59242]
> TREE01.47 ------- 34538 GPs (28.7817/s) [rcu: g236357 f0x0 total-gps=59375]
> TREE01.48 ------- 34491 GPs (28.7425/s) [rcu: g235029 f0x0 total-gps=59042]
> TREE01.49 ------- 36278 GPs (30.2317/s) [rcu: g245997 f0x0 total-gps=61784]
> TREE01.5 ------- 35415 GPs (29.5125/s) [rcu: g240741 f0x0 total-gps=60471]
> TREE01.50 ------- 34928 GPs (29.1067/s) [rcu: g236977 f0x0 total-gps=59529]
> TREE01.51 ------- 33065 GPs (27.5542/s) [rcu: g226573 f0x0 total-gps=56929]
> TREE01.52 ------- 34111 GPs (28.4258/s) [rcu: g234373 f0x0 total-gps=58878]
> TREE01.53 ------- 35627 GPs (29.6892/s) [rcu: g242797 f0x0 total-gps=60985]
> TREE01.54 ------- 33155 GPs (27.6292/s) [rcu: g227505 f0x0 total-gps=57160]
> TREE01.55 ------- 34386 GPs (28.655/s) [rcu: g233137 f0x0 total-gps=58571]
> TREE01.56 ------- 35130 GPs (29.275/s) [rcu: g237817 f0x0 total-gps=59740]
> TREE01.57 ------- 34660 GPs (28.8833/s) [rcu: g236409 f0x0 total-gps=59389]
> TREE01.58 ------- 34901 GPs (29.0842/s) [rcu: g236937 f0x0 total-gps=59521]
> TREE01.59 ------- 34915 GPs (29.0958/s) [rcu: g238425 f0x0 total-gps=59892]
> TREE01.6 ------- 35682 GPs (29.735/s) [rcu: g239305 f0x0 total-gps=60112]
> TREE01.60 ------- 34643 GPs (28.8692/s) [rcu: g235361 f0x0 total-gps=59126]
> TREE01.61 ------- 34343 GPs (28.6192/s) [rcu: g234857 f0x0 total-gps=58999]
> TREE01.62 ------- 34592 GPs (28.8267/s) [rcu: g233665 f0x0 total-gps=58703]
> TREE01.63 ------- 35315 GPs (29.4292/s) [rcu: g239865 f0x0 total-gps=60250]
> TREE01.64 ------- 34793 GPs (28.9942/s) [rcu: g240109 f0x0 total-gps=60311]
> TREE01.7 ------- 34171 GPs (28.4758/s) [rcu: g233977 f0x0 total-gps=58779]
> TREE01.8 ------- 33806 GPs (28.1717/s) [rcu: g233497 f0x0 total-gps=58659]
> TREE01.9 ------- 34083 GPs (28.4025/s) [rcu: g237765 f0x0 total-gps=59727]
> --- Done at Tue Oct 13 06:44:58 AM UTC 2026 (22:16.0) exitcode 0
>
> I miss those days where I could just turn around and ask Neeraj
> if this was correct at the office!
>
> --
> Thanks and Regards,
> Prateek
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [BUG] Apparent lost wakeups
2026-10-13 7:58 ` Vincent Guittot
@ 2026-10-13 8:30 ` K Prateek Nayak
2026-10-13 8:36 ` Vincent Guittot
2026-10-13 10:14 ` Peter Zijlstra
1 sibling, 1 reply; 18+ messages in thread
From: K Prateek Nayak @ 2026-10-13 8:30 UTC (permalink / raw)
To: Vincent Guittot
Cc: paulmck, Ingo Molnar, Peter Zijlstra, Juri Lelli,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, Joel Fernandes, linux-kernel, kernel-team
Hello Vincent,
On 10/13/2026 1:28 PM, Vincent Guittot wrote:
>> diff --git a/kernel/sched/core.c b/kernel/sched/core.c
>> index 0f63c30b4324..b37c771c41ad 100644
>> --- a/kernel/sched/core.c
>> +++ b/kernel/sched/core.c
>> @@ -11256,6 +11262,9 @@ void sched_change_end(struct sched_change_ctx *ctx)
> if (ctx->running) {
> /*
> * If this was a class promotion; let the old class
> * know it got preempted. Note that none of the
> * switch*_from() methods know the new class and none
> * of the switch*_to() methods know the old class.
> */
> if (sched_class_above(p->sched_class, ctx->class)) {
> rq->next_class->wakeup_preempt(rq, p, 0);
> rq->next_class = p->sched_class;
> }
> /*
> * If this was a degradation in class; make sure to
> * reschedule.
>> */
>> if (sched_class_above(ctx->class, p->sched_class))
>> resched_curr(rq);
>> + } else if (ctx->queued) {
>> + /* Check if we need can preempt the current task. */
>> + wakeup_preempt(rq, p, 0);
>
> Isn't what we have for ctx->running above what we also want in this case ?
Ack but with a couple of exceptions:
We may not need a resched_curr() on demotion since the queued task
isn't running.
For a queued but !running task, if the promotion lands at the same
class as rq->next_class, we'll need to do a
rq->next_class->wakeup_preempt() to check if we can run it now
which is also covered by wakeup_preempt().
The ctx->running case seems to have been optimized to avoid doing
an unnecessary resched / wakeup_preempt() calls when the current
task is still the best choice.
> Then sched_balance_newidle() will detect it with
>
> /* If a higher prio class was modified, restart the pick */
> if (rq_modified_above(this_rq, &fair_sched_class))
> pulled_task = -1;
Ack. I'm depending on wakeup_preempt() to set the rq->next_class
correctly on an upgrade and as you pointed, the newidle will then
force a RETRY_TASK.
I hope I'm not missing anything.
>
>> }
>> } else {
>> p->sched_class->prio_changed(rq, p, ctx->prio);
--
Thanks and Regards,
Prateek
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [BUG] Apparent lost wakeups
2026-10-13 8:30 ` K Prateek Nayak
@ 2026-10-13 8:36 ` Vincent Guittot
0 siblings, 0 replies; 18+ messages in thread
From: Vincent Guittot @ 2026-10-13 8:36 UTC (permalink / raw)
To: K Prateek Nayak
Cc: paulmck, Ingo Molnar, Peter Zijlstra, Juri Lelli,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, Joel Fernandes, linux-kernel, kernel-team
On Tue, 13 Oct 2026 at 10:31, K Prateek Nayak <kprateek.nayak@amd.com> wrote:
>
> Hello Vincent,
>
> On 10/13/2026 1:28 PM, Vincent Guittot wrote:
> >> diff --git a/kernel/sched/core.c b/kernel/sched/core.c
> >> index 0f63c30b4324..b37c771c41ad 100644
> >> --- a/kernel/sched/core.c
> >> +++ b/kernel/sched/core.c
> >> @@ -11256,6 +11262,9 @@ void sched_change_end(struct sched_change_ctx *ctx)
> > if (ctx->running) {
> > /*
> > * If this was a class promotion; let the old class
> > * know it got preempted. Note that none of the
> > * switch*_from() methods know the new class and none
> > * of the switch*_to() methods know the old class.
> > */
> > if (sched_class_above(p->sched_class, ctx->class)) {
> > rq->next_class->wakeup_preempt(rq, p, 0);
> > rq->next_class = p->sched_class;
> > }
> > /*
> > * If this was a degradation in class; make sure to
> > * reschedule.
> >> */
> >> if (sched_class_above(ctx->class, p->sched_class))
> >> resched_curr(rq);
> >> + } else if (ctx->queued) {
> >> + /* Check if we need can preempt the current task. */
> >> + wakeup_preempt(rq, p, 0);
> >
> > Isn't what we have for ctx->running above what we also want in this case ?
>
> Ack but with a couple of exceptions:
>
> We may not need a resched_curr() on demotion since the queued task
> isn't running.
I wanted to point the if (sched_class_above(p->sched_class,
ctx->class)) case. I agree that the demotion case is useless if not
running
>
> For a queued but !running task, if the promotion lands at the same
> class as rq->next_class, we'll need to do a
> rq->next_class->wakeup_preempt() to check if we can run it now
> which is also covered by wakeup_preempt().
>
> The ctx->running case seems to have been optimized to avoid doing
> an unnecessary resched / wakeup_preempt() calls when the current
> task is still the best choice.
>
> > Then sched_balance_newidle() will detect it with
> >
> > /* If a higher prio class was modified, restart the pick */
> > if (rq_modified_above(this_rq, &fair_sched_class))
> > pulled_task = -1;
>
> Ack. I'm depending on wakeup_preempt() to set the rq->next_class
> correctly on an upgrade and as you pointed, the newidle will then
> force a RETRY_TASK.
>
> I hope I'm not missing anything.
>
> >
> >> }
> >> } else {
> >> p->sched_class->prio_changed(rq, p, ctx->prio);
>
> --
> Thanks and Regards,
> Prateek
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [BUG] Apparent lost wakeups
2026-10-13 6:46 ` K Prateek Nayak
2026-10-13 7:58 ` Vincent Guittot
@ 2026-10-13 10:06 ` Peter Zijlstra
1 sibling, 0 replies; 18+ messages in thread
From: Peter Zijlstra @ 2026-10-13 10:06 UTC (permalink / raw)
To: K Prateek Nayak
Cc: paulmck, Ingo Molnar, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, Joel Fernandes, linux-kernel, kernel-team
On Tue, Oct 13, 2026 at 12:16:11PM +0530, K Prateek Nayak wrote:
> On 10/13/2026 11:36 AM, K Prateek Nayak wrote:
> >> The following command reproduces the lost-wakeup problem, which shows
> >> up as RCU CPU stall warnings caused by rcu_torture_reader() kthreads:
> >>
> >> tools/testing/selftests/rcutorture/bin/kvm.sh --allcpus --duration 20m --configs '19*TREE01' --bootargs "nohz=off rcutorture.onoff_interval=0 rcutorture.nocbs_nthreads=0 rcutorture.shuffle_interval=0" --trust-make
> >
> > tl;dr
> >
> > I could hit that WARN_ON() from your debug patch with the above test
> > even under rq_lock. Seemed like a missing resched_curr() but it
> > turned out to also be related to rq->next_class interaction.
> >
> > I haven't been able to hit the warnings with the below diff. Could
> > you please try this:
> >
> > diff --git a/kernel/sched/core.c b/kernel/sched/core.c
> > index 0f63c30b4324..f2f8af3cdfb5 100644
> > --- a/kernel/sched/core.c
> > +++ b/kernel/sched/core.c
> > @@ -11239,6 +11245,11 @@ void sched_change_end(struct sched_change_ctx *ctx)
> > if (p->sched_class->switched_to)
> > p->sched_class->switched_to(rq, p);
> >
> > + if (ctx->queued && sched_class_above(p->sched_class, rq->next_class)) {
> > + rq->next_class = p->sched_class;
> > + resched_curr(rq);
> > + }
> > +
> > if (ctx->running) {
> > /*
> > * If this was a class promotion; let the old class
> > ---
>
> And now that I've finally woken up just like that task, it can be
> simplified to:
>
> diff --git a/kernel/sched/core.c b/kernel/sched/core.c
> index 0f63c30b4324..b37c771c41ad 100644
> --- a/kernel/sched/core.c
> +++ b/kernel/sched/core.c
> @@ -11256,6 +11262,9 @@ void sched_change_end(struct sched_change_ctx *ctx)
> */
> if (sched_class_above(ctx->class, p->sched_class))
> resched_curr(rq);
> + } else if (ctx->queued) {
> + /* Check if we need can preempt the current task. */
> + wakeup_preempt(rq, p, 0);
> }
> } else {
> p->sched_class->prio_changed(rq, p, ctx->prio);
> ---
>
> Peter, what do you think?
I am confused is what.
So a non-running, but queued task is changing class and then somehow its
not getting scheduled?
So I perhaps see why the whole if (ctx->running) thing might be too
strong; I mean if a queued task gets promoted from OTHER to FIFO, we
need a preemption, but that's not the case at hand, is it?
Somehow we're getting stuck in IDLE with runnable tasks on; and I'm just
not seeing how we get there.
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [BUG] Apparent lost wakeups
2026-10-13 7:58 ` Vincent Guittot
2026-10-13 8:30 ` K Prateek Nayak
@ 2026-10-13 10:14 ` Peter Zijlstra
2026-10-13 10:29 ` K Prateek Nayak
1 sibling, 1 reply; 18+ messages in thread
From: Peter Zijlstra @ 2026-10-13 10:14 UTC (permalink / raw)
To: Vincent Guittot
Cc: K Prateek Nayak, paulmck, Ingo Molnar, Juri Lelli,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, Joel Fernandes, linux-kernel, kernel-team
On Tue, Oct 13, 2026 at 09:58:25AM +0200, Vincent Guittot wrote:
> > diff --git a/kernel/sched/core.c b/kernel/sched/core.c
> > index 0f63c30b4324..b37c771c41ad 100644
> > --- a/kernel/sched/core.c
> > +++ b/kernel/sched/core.c
> > @@ -11256,6 +11262,9 @@ void sched_change_end(struct sched_change_ctx *ctx)
> if (ctx->running) {
> /*
> * If this was a class promotion; let the old class
> * know it got preempted. Note that none of the
> * switch*_from() methods know the new class and none
> * of the switch*_to() methods know the old class.
> */
> if (sched_class_above(p->sched_class, ctx->class)) {
> rq->next_class->wakeup_preempt(rq, p, 0);
> rq->next_class = p->sched_class;
> }
> /*
> * If this was a degradation in class; make sure to
> * reschedule.
> > */
> > if (sched_class_above(ctx->class, p->sched_class))
> > resched_curr(rq);
> > + } else if (ctx->queued) {
> > + /* Check if we need can preempt the current task. */
> > + wakeup_preempt(rq, p, 0);
>
> Isn't what we have for ctx->running above what we also want in this case ?
> Then sched_balance_newidle() will detect it with
>
> /* If a higher prio class was modified, restart the pick */
> if (rq_modified_above(this_rq, &fair_sched_class))
> pulled_task = -1;
>
Ah, I'm maybe slowly starting to see the shape of this. Does something
like so make sense?
That is, any task on the rq cares about promotion, only the current
running task cares about demotion.
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 047fbe8ef298..2a8bf562a769 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -11261,7 +11261,7 @@ void sched_change_end(struct sched_change_ctx *ctx)
if (p->sched_class->switched_to)
p->sched_class->switched_to(rq, p);
- if (ctx->running) {
+ if (ctx->queued) {
/*
* If this was a class promotion; let the old class
* know it got preempted. Note that none of the
@@ -11272,6 +11272,9 @@ void sched_change_end(struct sched_change_ctx *ctx)
rq->next_class->wakeup_preempt(rq, p, 0);
rq->next_class = p->sched_class;
}
+ }
+
+ if (ctx->running) {
/*
* If this was a degradation in class; make sure to
* reschedule.
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [BUG] Apparent lost wakeups
2026-10-13 10:14 ` Peter Zijlstra
@ 2026-10-13 10:29 ` K Prateek Nayak
2026-10-13 10:37 ` Peter Zijlstra
0 siblings, 1 reply; 18+ messages in thread
From: K Prateek Nayak @ 2026-10-13 10:29 UTC (permalink / raw)
To: Peter Zijlstra, Vincent Guittot
Cc: paulmck, Ingo Molnar, Juri Lelli, Dietmar Eggemann,
Steven Rostedt, Ben Segall, Mel Gorman, Valentin Schneider,
Joel Fernandes, linux-kernel, kernel-team
Hello Peter,
On 10/13/2026 3:44 PM, Peter Zijlstra wrote:
> On Tue, Oct 13, 2026 at 09:58:25AM +0200, Vincent Guittot wrote:
>
>>> diff --git a/kernel/sched/core.c b/kernel/sched/core.c
>>> index 0f63c30b4324..b37c771c41ad 100644
>>> --- a/kernel/sched/core.c
>>> +++ b/kernel/sched/core.c
>>> @@ -11256,6 +11262,9 @@ void sched_change_end(struct sched_change_ctx *ctx)
>> if (ctx->running) {
>> /*
>> * If this was a class promotion; let the old class
>> * know it got preempted. Note that none of the
>> * switch*_from() methods know the new class and none
>> * of the switch*_to() methods know the old class.
>> */
>> if (sched_class_above(p->sched_class, ctx->class)) {
>> rq->next_class->wakeup_preempt(rq, p, 0);
>> rq->next_class = p->sched_class;
>> }
>> /*
>> * If this was a degradation in class; make sure to
>> * reschedule.
>>> */
>>> if (sched_class_above(ctx->class, p->sched_class))
>>> resched_curr(rq);
>>> + } else if (ctx->queued) {
>>> + /* Check if we need can preempt the current task. */
>>> + wakeup_preempt(rq, p, 0);
>>
>> Isn't what we have for ctx->running above what we also want in this case ?
>> Then sched_balance_newidle() will detect it with
>>
>> /* If a higher prio class was modified, restart the pick */
>> if (rq_modified_above(this_rq, &fair_sched_class))
>> pulled_task = -1;
>>
>
> Ah, I'm maybe slowly starting to see the shape of this.
Essentially:
/* rq->next_class starts as fair_sched_class */
pick_next_task()
/* Finishes ->pick_task() for stop, dl, rt */
pick_task_fair()
sched_balance_newidle()
raw_spin_rq_unlock()
activate_task(rq0, p) /* fair */
/* p->on_rq but not curr */
rq->next_class = fair_sched_class;
__sched_setscheduler()
/* Upgrades p to FIFO */
sched_change_end()
/* Does not change rq->next_class */
rq_unlock()
raw_spin_rq_lock()
if (this_rq->cfs.h_nr_queued)
/* Skipped! No fair task */
if (rq_modified_above(this_rq, &fair_sched_class))
/* Skipped: rq->nex_class is still fair */
return 0;
return NULL;
pick_task_idle()
return rq->idle;
/* In __schedule() */
clear_tsk_need_resched();
/* Goes idle with rq->nr_running = 1 and no need_resched() */
> Does something like so make sense?
Couple nits below.
>
> That is, any task on the rq cares about promotion, only the current
> running task cares about demotion.
>
> diff --git a/kernel/sched/core.c b/kernel/sched/core.c
> index 047fbe8ef298..2a8bf562a769 100644
> --- a/kernel/sched/core.c
> +++ b/kernel/sched/core.c
> @@ -11261,7 +11261,7 @@ void sched_change_end(struct sched_change_ctx *ctx)
> if (p->sched_class->switched_to)
> p->sched_class->switched_to(rq, p);
>
> - if (ctx->running) {
> + if (ctx->queued) {
I think this branch should just call wakeup_preempt() to account
for p->sched_class == rq->next_class in case p is upgraded to
same class as rq->curr.
> /*
> * If this was a class promotion; let the old class
> * know it got preempted. Note that none of the
> @@ -11272,6 +11272,9 @@ void sched_change_end(struct sched_change_ctx *ctx)
> rq->next_class->wakeup_preempt(rq, p, 0);
> rq->next_class = p->sched_class;
> }
> + }
> +
> + if (ctx->running) {
> /*
> * If this was a degradation in class; make sure to
> * reschedule.
This looks correct. Upgrade keeps the task running and degrade
re-evaluates the selection.
--
Thanks and Regards,
Prateek
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [BUG] Apparent lost wakeups
2026-10-13 10:29 ` K Prateek Nayak
@ 2026-10-13 10:37 ` Peter Zijlstra
2026-10-13 10:43 ` K Prateek Nayak
0 siblings, 1 reply; 18+ messages in thread
From: Peter Zijlstra @ 2026-10-13 10:37 UTC (permalink / raw)
To: K Prateek Nayak
Cc: Vincent Guittot, paulmck, Ingo Molnar, Juri Lelli,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, Joel Fernandes, linux-kernel, kernel-team
On Tue, Oct 13, 2026 at 03:59:36PM +0530, K Prateek Nayak wrote:
> Essentially:
>
> /* rq->next_class starts as fair_sched_class */
>
> pick_next_task()
> /* Finishes ->pick_task() for stop, dl, rt */
> pick_task_fair()
> sched_balance_newidle()
> raw_spin_rq_unlock()
> activate_task(rq0, p) /* fair */
> /* p->on_rq but not curr */
> rq->next_class = fair_sched_class;
>
> __sched_setscheduler()
> /* Upgrades p to FIFO */
> sched_change_end()
> /* Does not change rq->next_class */
> rq_unlock()
>
> raw_spin_rq_lock()
>
> if (this_rq->cfs.h_nr_queued)
> /* Skipped! No fair task */
>
> if (rq_modified_above(this_rq, &fair_sched_class))
> /* Skipped: rq->nex_class is still fair */
>
> return 0;
> return NULL;
>
> pick_task_idle()
> return rq->idle;
>
> /* In __schedule() */
> clear_tsk_need_resched();
>
> /* Goes idle with rq->nr_running = 1 and no need_resched() */
Excellent Changelog material that ;-)
>
> > Does something like so make sense?
>
> Couple nits below.
>
> >
> > That is, any task on the rq cares about promotion, only the current
> > running task cares about demotion.
> >
> > diff --git a/kernel/sched/core.c b/kernel/sched/core.c
> > index 047fbe8ef298..2a8bf562a769 100644
> > --- a/kernel/sched/core.c
> > +++ b/kernel/sched/core.c
> > @@ -11261,7 +11261,7 @@ void sched_change_end(struct sched_change_ctx *ctx)
> > if (p->sched_class->switched_to)
> > p->sched_class->switched_to(rq, p);
> >
> > - if (ctx->running) {
> > + if (ctx->queued) {
>
> I think this branch should just call wakeup_preempt() to account
> for p->sched_class == rq->next_class in case p is upgraded to
> same class as rq->curr.
Yes, that'll do.
> > /*
> > * If this was a class promotion; let the old class
> > * know it got preempted. Note that none of the
> > @@ -11272,6 +11272,9 @@ void sched_change_end(struct sched_change_ctx *ctx)
> > rq->next_class->wakeup_preempt(rq, p, 0);
> > rq->next_class = p->sched_class;
> > }
> > + }
> > +
> > + if (ctx->running) {
> > /*
> > * If this was a degradation in class; make sure to
> > * reschedule.
>
> This looks correct. Upgrade keeps the task running and degrade
> re-evaluates the selection.
Can you turn all this into a nice patch -- you've done all the hard work
on it anyway.
Thanks!
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [BUG] Apparent lost wakeups
2026-10-13 10:37 ` Peter Zijlstra
@ 2026-10-13 10:43 ` K Prateek Nayak
0 siblings, 0 replies; 18+ messages in thread
From: K Prateek Nayak @ 2026-10-13 10:43 UTC (permalink / raw)
To: Peter Zijlstra
Cc: Vincent Guittot, paulmck, Ingo Molnar, Juri Lelli,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, Joel Fernandes, linux-kernel, kernel-team
On 10/13/2026 4:07 PM, Peter Zijlstra wrote:
> Can you turn all this into a nice patch -- you've done all the hard work
> on it anyway.
Sure! I'll run TREE01 for sanity on my end and send the patch out
later in the evening. Should give Paul some time to test and confirm
that we are seeing the same issue :-)
--
Thanks and Regards,
Prateek
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH] sched/core: Call wakeup_preempt() for queued tasks during class change
2026-10-12 18:41 [BUG] Apparent lost wakeups Paul E. McKenney
2026-10-13 6:06 ` K Prateek Nayak
@ 2026-10-13 15:24 ` K Prateek Nayak
2026-10-13 16:45 ` Vincent Guittot
2026-10-13 17:57 ` Paul E. McKenney
1 sibling, 2 replies; 18+ messages in thread
From: K Prateek Nayak @ 2026-10-13 15:24 UTC (permalink / raw)
To: Paul E. McKenney, Ingo Molnar, Peter Zijlstra, Juri Lelli,
Vincent Guittot
Cc: Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, Joel Fernandes, linux-kernel, kernel-team,
K Prateek Nayak
Paul reported recent RCU changes exposed a lost resched where CPU idles
with a rcutorture thread waiting while being completely runnable.
Dumping the rq state at the time of idling with runnable tasks showed
the following:
rq->next_class = &fair_sched_class
rq->nr_running = 1
rq->cfs.h_nr_queued = 0
rq->rt.rt_nr_running = 1
Although the waiting task was an RT task, "rq->next_class" did not point
to rt_sched_class and was stuck at the lower priority fair_sched_class.
Inspecting sched_change_end() revealed the following race with
__sched_setscheduler() for queued, but non-running tasks:
CPU0 CPU1
==== ====
__schedule()
/*
* rq->donor is a fair task that blocks.
* rq->next_class = &fair_sched_class
*/
rq_lock(rq0)
pick_next_task()
/* nr_running = 0 */
pick_task_fair()
sched_balance_newidle()
raw_spin_rq_unlock(rq0)
rq_lock(rq0)
activate_task(rq0, p)
/* p is a fair task */
rq0->nr_running++;
rq0->cfs.h_nr_queued++;
wakeup_preempt(rq, p)
rq->next_class = &fair_sched_class
resched_curr()
rq_unlock(rq0)
__sched_setscheduler(p)
/* p is moved to SCHED_RR */
task_rq_lock(p)
sched_change_begin()
rq0->cfs.h_nr_queued--;
rq0->rt.rt_nr_running++;
sched_change_end()
/*
* p->on_rq = 1 (ctx->queued)
* but p is not rq->donor
* (!ctx->running) hence
* resched_curr() is and
* rq->next_class upgrade is
* skipped.
*/
task_rq_unlock()
raw_spin_rq_lock(rq0)
/*
* rq->nr_running = 1
* rq->cfs.h_nr_queued = 0
* rq->rt.rt_nr_running = 1
* rq->next_class = &fair_sched_class
*/
if (rq->cfs.h_nr_queued && ...)
/* Above is false */
if (rq_modified_above(this_rq, &fair_sched_class))
/* Also false since rq->next_class is still fair */
/*
* sched_balance_newidle() is considered to
* have failed to pull any fair tasks and no
* other wakeups were detected.
*/
return NULL; /* pick_task_fair() */
pick_task_idle()
return rq->idle;
next = rq->idle;
/* Preemption indicators are lost */
clear_tsk_need_resched(prev);
clear_preempt_need_resched();
rq->curr = rq->idle;
!!! rq->idle switches in with rq->running == 1 but !need_resched() !!!
In simple words, enqueue, followed by a class change of the newly queued
task goes unnoticed in rq->next_class indicator and
sched_balance_newidle() sees no fair task, nor any higher priority task
enqueued.
The above sequence will lead to CPU idling with a runnable task on the
CPU.
Call wakeup_preempt() for ctx->queued cases in sched_change_end() to
re-evaluate rq->next_class after class change (ENQUEUE_CLASS) and set
the NEED_RESCHED indicator accordingly.
Newidle balance will correctly see an enqueue of a higher priority
task if a queued task was upgraded when the rq_lock() was dropped and
will retry the pick.
Reported-by: "Paul E. McKenney" <paulmck@kernel.org>
Closes: https://lore.kernel.org/lkml/8fa7f795-518c-4507-b684-60b0c1cac2c5@paulmck-laptop/
Fixes: 1e900f415c60 ("sched: Detect per-class runqueue changes")
Signed-off-by: K Prateek Nayak <kprateek.nayak@amd.com>
---
Patches are based on tip:sched/urgent at commit a0bb6fac53fa
("sched/core: Account PSI IRQ time to the execution context, not the
scheduling context").
Tested with sched-messaging and Paul's TREE01 reproducer.
---
kernel/sched/core.c | 19 +++++++++----------
1 file changed, 9 insertions(+), 10 deletions(-)
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 1fe40de6ebe3..1754d9f95f2c 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -11327,17 +11327,16 @@ void sched_change_end(struct sched_change_ctx *ctx)
if (p->sched_class->switched_to)
p->sched_class->switched_to(rq, p);
+ /*
+ * Check if the task can preempt the current in case the class
+ * was upgraded. Note that none of the switch*_from() methods
+ * know the new class and none of the switch*_to() methods know
+ * the old class so we must resched here.
+ */
+ if (ctx->queued)
+ wakeup_preempt(rq, p, 0);
+
if (ctx->running) {
- /*
- * If this was a class promotion; let the old class
- * know it got preempted. Note that none of the
- * switch*_from() methods know the new class and none
- * of the switch*_to() methods know the old class.
- */
- if (sched_class_above(p->sched_class, ctx->class)) {
- rq->next_class->wakeup_preempt(rq, p, 0);
- rq->next_class = p->sched_class;
- }
/*
* If this was a degradation in class; make sure to
* reschedule.
base-commit: a0bb6fac53fa7cf1cadb487b43d4c9276a6b82e3
--
2.34.1
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH] sched/core: Call wakeup_preempt() for queued tasks during class change
2026-10-13 15:24 ` [PATCH] sched/core: Call wakeup_preempt() for queued tasks during class change K Prateek Nayak
@ 2026-10-13 16:45 ` Vincent Guittot
2026-10-13 17:57 ` Paul E. McKenney
1 sibling, 0 replies; 18+ messages in thread
From: Vincent Guittot @ 2026-10-13 16:45 UTC (permalink / raw)
To: K Prateek Nayak
Cc: Paul E. McKenney, Ingo Molnar, Peter Zijlstra, Juri Lelli,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, Joel Fernandes, linux-kernel, kernel-team
On Tue, 13 Oct 2026 at 17:25, K Prateek Nayak <kprateek.nayak@amd.com> wrote:
>
> Paul reported recent RCU changes exposed a lost resched where CPU idles
> with a rcutorture thread waiting while being completely runnable.
>
> Dumping the rq state at the time of idling with runnable tasks showed
> the following:
>
> rq->next_class = &fair_sched_class
> rq->nr_running = 1
> rq->cfs.h_nr_queued = 0
> rq->rt.rt_nr_running = 1
>
> Although the waiting task was an RT task, "rq->next_class" did not point
> to rt_sched_class and was stuck at the lower priority fair_sched_class.
>
> Inspecting sched_change_end() revealed the following race with
> __sched_setscheduler() for queued, but non-running tasks:
>
> CPU0 CPU1
> ==== ====
>
> __schedule()
>
> /*
> * rq->donor is a fair task that blocks.
> * rq->next_class = &fair_sched_class
> */
>
> rq_lock(rq0)
>
> pick_next_task()
> /* nr_running = 0 */
> pick_task_fair()
> sched_balance_newidle()
> raw_spin_rq_unlock(rq0)
> rq_lock(rq0)
>
> activate_task(rq0, p)
> /* p is a fair task */
> rq0->nr_running++;
> rq0->cfs.h_nr_queued++;
> wakeup_preempt(rq, p)
> rq->next_class = &fair_sched_class
> resched_curr()
>
> rq_unlock(rq0)
>
> __sched_setscheduler(p)
> /* p is moved to SCHED_RR */
> task_rq_lock(p)
> sched_change_begin()
>
> rq0->cfs.h_nr_queued--;
> rq0->rt.rt_nr_running++;
>
> sched_change_end()
> /*
> * p->on_rq = 1 (ctx->queued)
> * but p is not rq->donor
> * (!ctx->running) hence
> * resched_curr() is and
> * rq->next_class upgrade is
> * skipped.
> */
>
> task_rq_unlock()
>
> raw_spin_rq_lock(rq0)
>
> /*
> * rq->nr_running = 1
> * rq->cfs.h_nr_queued = 0
> * rq->rt.rt_nr_running = 1
> * rq->next_class = &fair_sched_class
> */
>
> if (rq->cfs.h_nr_queued && ...)
> /* Above is false */
>
> if (rq_modified_above(this_rq, &fair_sched_class))
> /* Also false since rq->next_class is still fair */
>
> /*
> * sched_balance_newidle() is considered to
> * have failed to pull any fair tasks and no
> * other wakeups were detected.
> */
> return NULL; /* pick_task_fair() */
>
> pick_task_idle()
> return rq->idle;
>
> next = rq->idle;
>
> /* Preemption indicators are lost */
> clear_tsk_need_resched(prev);
> clear_preempt_need_resched();
>
> rq->curr = rq->idle;
>
> !!! rq->idle switches in with rq->running == 1 but !need_resched() !!!
>
> In simple words, enqueue, followed by a class change of the newly queued
> task goes unnoticed in rq->next_class indicator and
> sched_balance_newidle() sees no fair task, nor any higher priority task
> enqueued.
>
> The above sequence will lead to CPU idling with a runnable task on the
> CPU.
>
> Call wakeup_preempt() for ctx->queued cases in sched_change_end() to
> re-evaluate rq->next_class after class change (ENQUEUE_CLASS) and set
> the NEED_RESCHED indicator accordingly.
>
> Newidle balance will correctly see an enqueue of a higher priority
> task if a queued task was upgraded when the rq_lock() was dropped and
> will retry the pick.
>
> Reported-by: "Paul E. McKenney" <paulmck@kernel.org>
> Closes: https://lore.kernel.org/lkml/8fa7f795-518c-4507-b684-60b0c1cac2c5@paulmck-laptop/
> Fixes: 1e900f415c60 ("sched: Detect per-class runqueue changes")
> Signed-off-by: K Prateek Nayak <kprateek.nayak@amd.com>
Reviewed-by: Vincent Guittot <vincent.guittot@linaro.org>
> ---
> Patches are based on tip:sched/urgent at commit a0bb6fac53fa
> ("sched/core: Account PSI IRQ time to the execution context, not the
> scheduling context").
>
> Tested with sched-messaging and Paul's TREE01 reproducer.
> ---
> kernel/sched/core.c | 19 +++++++++----------
> 1 file changed, 9 insertions(+), 10 deletions(-)
>
> diff --git a/kernel/sched/core.c b/kernel/sched/core.c
> index 1fe40de6ebe3..1754d9f95f2c 100644
> --- a/kernel/sched/core.c
> +++ b/kernel/sched/core.c
> @@ -11327,17 +11327,16 @@ void sched_change_end(struct sched_change_ctx *ctx)
> if (p->sched_class->switched_to)
> p->sched_class->switched_to(rq, p);
>
> + /*
> + * Check if the task can preempt the current in case the class
> + * was upgraded. Note that none of the switch*_from() methods
> + * know the new class and none of the switch*_to() methods know
> + * the old class so we must resched here.
> + */
> + if (ctx->queued)
> + wakeup_preempt(rq, p, 0);
> +
> if (ctx->running) {
> - /*
> - * If this was a class promotion; let the old class
> - * know it got preempted. Note that none of the
> - * switch*_from() methods know the new class and none
> - * of the switch*_to() methods know the old class.
> - */
> - if (sched_class_above(p->sched_class, ctx->class)) {
> - rq->next_class->wakeup_preempt(rq, p, 0);
> - rq->next_class = p->sched_class;
> - }
> /*
> * If this was a degradation in class; make sure to
> * reschedule.
>
> base-commit: a0bb6fac53fa7cf1cadb487b43d4c9276a6b82e3
> --
> 2.34.1
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH] sched/core: Call wakeup_preempt() for queued tasks during class change
2026-10-13 15:24 ` [PATCH] sched/core: Call wakeup_preempt() for queued tasks during class change K Prateek Nayak
2026-10-13 16:45 ` Vincent Guittot
@ 2026-10-13 17:57 ` Paul E. McKenney
2026-10-13 19:55 ` Paul E. McKenney
1 sibling, 1 reply; 18+ messages in thread
From: Paul E. McKenney @ 2026-10-13 17:57 UTC (permalink / raw)
To: K Prateek Nayak
Cc: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, Joel Fernandes, linux-kernel, kernel-team
On Tue, Oct 13, 2026 at 03:24:55PM +0000, K Prateek Nayak wrote:
> Paul reported recent RCU changes exposed a lost resched where CPU idles
> with a rcutorture thread waiting while being completely runnable.
>
> Dumping the rq state at the time of idling with runnable tasks showed
> the following:
>
> rq->next_class = &fair_sched_class
> rq->nr_running = 1
> rq->cfs.h_nr_queued = 0
> rq->rt.rt_nr_running = 1
>
> Although the waiting task was an RT task, "rq->next_class" did not point
> to rt_sched_class and was stuck at the lower priority fair_sched_class.
Thank you all very much! I have started a short test. If all goes well
with the short tests, I will run an overnight test.
I will of course let you know how the individual tests go.
Thanx, Paul
> Inspecting sched_change_end() revealed the following race with
> __sched_setscheduler() for queued, but non-running tasks:
>
> CPU0 CPU1
> ==== ====
>
> __schedule()
>
> /*
> * rq->donor is a fair task that blocks.
> * rq->next_class = &fair_sched_class
> */
>
> rq_lock(rq0)
>
> pick_next_task()
> /* nr_running = 0 */
> pick_task_fair()
> sched_balance_newidle()
> raw_spin_rq_unlock(rq0)
> rq_lock(rq0)
>
> activate_task(rq0, p)
> /* p is a fair task */
> rq0->nr_running++;
> rq0->cfs.h_nr_queued++;
> wakeup_preempt(rq, p)
> rq->next_class = &fair_sched_class
> resched_curr()
>
> rq_unlock(rq0)
>
> __sched_setscheduler(p)
> /* p is moved to SCHED_RR */
> task_rq_lock(p)
> sched_change_begin()
>
> rq0->cfs.h_nr_queued--;
> rq0->rt.rt_nr_running++;
>
> sched_change_end()
> /*
> * p->on_rq = 1 (ctx->queued)
> * but p is not rq->donor
> * (!ctx->running) hence
> * resched_curr() is and
> * rq->next_class upgrade is
> * skipped.
> */
>
> task_rq_unlock()
>
> raw_spin_rq_lock(rq0)
>
> /*
> * rq->nr_running = 1
> * rq->cfs.h_nr_queued = 0
> * rq->rt.rt_nr_running = 1
> * rq->next_class = &fair_sched_class
> */
>
> if (rq->cfs.h_nr_queued && ...)
> /* Above is false */
>
> if (rq_modified_above(this_rq, &fair_sched_class))
> /* Also false since rq->next_class is still fair */
>
> /*
> * sched_balance_newidle() is considered to
> * have failed to pull any fair tasks and no
> * other wakeups were detected.
> */
> return NULL; /* pick_task_fair() */
>
> pick_task_idle()
> return rq->idle;
>
> next = rq->idle;
>
> /* Preemption indicators are lost */
> clear_tsk_need_resched(prev);
> clear_preempt_need_resched();
>
> rq->curr = rq->idle;
>
> !!! rq->idle switches in with rq->running == 1 but !need_resched() !!!
>
> In simple words, enqueue, followed by a class change of the newly queued
> task goes unnoticed in rq->next_class indicator and
> sched_balance_newidle() sees no fair task, nor any higher priority task
> enqueued.
>
> The above sequence will lead to CPU idling with a runnable task on the
> CPU.
>
> Call wakeup_preempt() for ctx->queued cases in sched_change_end() to
> re-evaluate rq->next_class after class change (ENQUEUE_CLASS) and set
> the NEED_RESCHED indicator accordingly.
>
> Newidle balance will correctly see an enqueue of a higher priority
> task if a queued task was upgraded when the rq_lock() was dropped and
> will retry the pick.
>
> Reported-by: "Paul E. McKenney" <paulmck@kernel.org>
> Closes: https://lore.kernel.org/lkml/8fa7f795-518c-4507-b684-60b0c1cac2c5@paulmck-laptop/
> Fixes: 1e900f415c60 ("sched: Detect per-class runqueue changes")
> Signed-off-by: K Prateek Nayak <kprateek.nayak@amd.com>
> ---
> Patches are based on tip:sched/urgent at commit a0bb6fac53fa
> ("sched/core: Account PSI IRQ time to the execution context, not the
> scheduling context").
>
> Tested with sched-messaging and Paul's TREE01 reproducer.
> ---
> kernel/sched/core.c | 19 +++++++++----------
> 1 file changed, 9 insertions(+), 10 deletions(-)
>
> diff --git a/kernel/sched/core.c b/kernel/sched/core.c
> index 1fe40de6ebe3..1754d9f95f2c 100644
> --- a/kernel/sched/core.c
> +++ b/kernel/sched/core.c
> @@ -11327,17 +11327,16 @@ void sched_change_end(struct sched_change_ctx *ctx)
> if (p->sched_class->switched_to)
> p->sched_class->switched_to(rq, p);
>
> + /*
> + * Check if the task can preempt the current in case the class
> + * was upgraded. Note that none of the switch*_from() methods
> + * know the new class and none of the switch*_to() methods know
> + * the old class so we must resched here.
> + */
> + if (ctx->queued)
> + wakeup_preempt(rq, p, 0);
> +
> if (ctx->running) {
> - /*
> - * If this was a class promotion; let the old class
> - * know it got preempted. Note that none of the
> - * switch*_from() methods know the new class and none
> - * of the switch*_to() methods know the old class.
> - */
> - if (sched_class_above(p->sched_class, ctx->class)) {
> - rq->next_class->wakeup_preempt(rq, p, 0);
> - rq->next_class = p->sched_class;
> - }
> /*
> * If this was a degradation in class; make sure to
> * reschedule.
>
> base-commit: a0bb6fac53fa7cf1cadb487b43d4c9276a6b82e3
> --
> 2.34.1
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH] sched/core: Call wakeup_preempt() for queued tasks during class change
2026-10-13 17:57 ` Paul E. McKenney
@ 2026-10-13 19:55 ` Paul E. McKenney
2026-10-13 20:06 ` Paul E. McKenney
0 siblings, 1 reply; 18+ messages in thread
From: Paul E. McKenney @ 2026-10-13 19:55 UTC (permalink / raw)
To: K Prateek Nayak
Cc: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, Joel Fernandes, linux-kernel, kernel-team
On Tue, Oct 13, 2026 at 10:57:17AM -0700, Paul E. McKenney wrote:
> On Tue, Oct 13, 2026 at 03:24:55PM +0000, K Prateek Nayak wrote:
> > Paul reported recent RCU changes exposed a lost resched where CPU idles
> > with a rcutorture thread waiting while being completely runnable.
> >
> > Dumping the rq state at the time of idling with runnable tasks showed
> > the following:
> >
> > rq->next_class = &fair_sched_class
> > rq->nr_running = 1
> > rq->cfs.h_nr_queued = 0
> > rq->rt.rt_nr_running = 1
> >
> > Although the waiting task was an RT task, "rq->next_class" did not point
> > to rt_sched_class and was stuck at the lower priority fair_sched_class.
>
> Thank you all very much! I have started a short test. If all goes well
> with the short tests, I will run an overnight test.
>
> I will of course let you know how the individual tests go.
20 minutes of 39*TREE03 (13 hours) on x86 passed! 2 hours of 9*TREE03 (18
hours) on arm64 in progress. Starting five hours of 200*TREE03 on x86.
If that works, 12 hours of same.
Thanx, Paul
> > Inspecting sched_change_end() revealed the following race with
> > __sched_setscheduler() for queued, but non-running tasks:
> >
> > CPU0 CPU1
> > ==== ====
> >
> > __schedule()
> >
> > /*
> > * rq->donor is a fair task that blocks.
> > * rq->next_class = &fair_sched_class
> > */
> >
> > rq_lock(rq0)
> >
> > pick_next_task()
> > /* nr_running = 0 */
> > pick_task_fair()
> > sched_balance_newidle()
> > raw_spin_rq_unlock(rq0)
> > rq_lock(rq0)
> >
> > activate_task(rq0, p)
> > /* p is a fair task */
> > rq0->nr_running++;
> > rq0->cfs.h_nr_queued++;
> > wakeup_preempt(rq, p)
> > rq->next_class = &fair_sched_class
> > resched_curr()
> >
> > rq_unlock(rq0)
> >
> > __sched_setscheduler(p)
> > /* p is moved to SCHED_RR */
> > task_rq_lock(p)
> > sched_change_begin()
> >
> > rq0->cfs.h_nr_queued--;
> > rq0->rt.rt_nr_running++;
> >
> > sched_change_end()
> > /*
> > * p->on_rq = 1 (ctx->queued)
> > * but p is not rq->donor
> > * (!ctx->running) hence
> > * resched_curr() is and
> > * rq->next_class upgrade is
> > * skipped.
> > */
> >
> > task_rq_unlock()
> >
> > raw_spin_rq_lock(rq0)
> >
> > /*
> > * rq->nr_running = 1
> > * rq->cfs.h_nr_queued = 0
> > * rq->rt.rt_nr_running = 1
> > * rq->next_class = &fair_sched_class
> > */
> >
> > if (rq->cfs.h_nr_queued && ...)
> > /* Above is false */
> >
> > if (rq_modified_above(this_rq, &fair_sched_class))
> > /* Also false since rq->next_class is still fair */
> >
> > /*
> > * sched_balance_newidle() is considered to
> > * have failed to pull any fair tasks and no
> > * other wakeups were detected.
> > */
> > return NULL; /* pick_task_fair() */
> >
> > pick_task_idle()
> > return rq->idle;
> >
> > next = rq->idle;
> >
> > /* Preemption indicators are lost */
> > clear_tsk_need_resched(prev);
> > clear_preempt_need_resched();
> >
> > rq->curr = rq->idle;
> >
> > !!! rq->idle switches in with rq->running == 1 but !need_resched() !!!
> >
> > In simple words, enqueue, followed by a class change of the newly queued
> > task goes unnoticed in rq->next_class indicator and
> > sched_balance_newidle() sees no fair task, nor any higher priority task
> > enqueued.
> >
> > The above sequence will lead to CPU idling with a runnable task on the
> > CPU.
> >
> > Call wakeup_preempt() for ctx->queued cases in sched_change_end() to
> > re-evaluate rq->next_class after class change (ENQUEUE_CLASS) and set
> > the NEED_RESCHED indicator accordingly.
> >
> > Newidle balance will correctly see an enqueue of a higher priority
> > task if a queued task was upgraded when the rq_lock() was dropped and
> > will retry the pick.
> >
> > Reported-by: "Paul E. McKenney" <paulmck@kernel.org>
> > Closes: https://lore.kernel.org/lkml/8fa7f795-518c-4507-b684-60b0c1cac2c5@paulmck-laptop/
> > Fixes: 1e900f415c60 ("sched: Detect per-class runqueue changes")
> > Signed-off-by: K Prateek Nayak <kprateek.nayak@amd.com>
> > ---
> > Patches are based on tip:sched/urgent at commit a0bb6fac53fa
> > ("sched/core: Account PSI IRQ time to the execution context, not the
> > scheduling context").
> >
> > Tested with sched-messaging and Paul's TREE01 reproducer.
> > ---
> > kernel/sched/core.c | 19 +++++++++----------
> > 1 file changed, 9 insertions(+), 10 deletions(-)
> >
> > diff --git a/kernel/sched/core.c b/kernel/sched/core.c
> > index 1fe40de6ebe3..1754d9f95f2c 100644
> > --- a/kernel/sched/core.c
> > +++ b/kernel/sched/core.c
> > @@ -11327,17 +11327,16 @@ void sched_change_end(struct sched_change_ctx *ctx)
> > if (p->sched_class->switched_to)
> > p->sched_class->switched_to(rq, p);
> >
> > + /*
> > + * Check if the task can preempt the current in case the class
> > + * was upgraded. Note that none of the switch*_from() methods
> > + * know the new class and none of the switch*_to() methods know
> > + * the old class so we must resched here.
> > + */
> > + if (ctx->queued)
> > + wakeup_preempt(rq, p, 0);
> > +
> > if (ctx->running) {
> > - /*
> > - * If this was a class promotion; let the old class
> > - * know it got preempted. Note that none of the
> > - * switch*_from() methods know the new class and none
> > - * of the switch*_to() methods know the old class.
> > - */
> > - if (sched_class_above(p->sched_class, ctx->class)) {
> > - rq->next_class->wakeup_preempt(rq, p, 0);
> > - rq->next_class = p->sched_class;
> > - }
> > /*
> > * If this was a degradation in class; make sure to
> > * reschedule.
> >
> > base-commit: a0bb6fac53fa7cf1cadb487b43d4c9276a6b82e3
> > --
> > 2.34.1
> >
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH] sched/core: Call wakeup_preempt() for queued tasks during class change
2026-10-13 19:55 ` Paul E. McKenney
@ 2026-10-13 20:06 ` Paul E. McKenney
0 siblings, 0 replies; 18+ messages in thread
From: Paul E. McKenney @ 2026-10-13 20:06 UTC (permalink / raw)
To: K Prateek Nayak
Cc: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, Joel Fernandes, linux-kernel, kernel-team
On Tue, Oct 13, 2026 at 12:55:12PM -0700, Paul E. McKenney wrote:
> On Tue, Oct 13, 2026 at 10:57:17AM -0700, Paul E. McKenney wrote:
> > On Tue, Oct 13, 2026 at 03:24:55PM +0000, K Prateek Nayak wrote:
> > > Paul reported recent RCU changes exposed a lost resched where CPU idles
> > > with a rcutorture thread waiting while being completely runnable.
> > >
> > > Dumping the rq state at the time of idling with runnable tasks showed
> > > the following:
> > >
> > > rq->next_class = &fair_sched_class
> > > rq->nr_running = 1
> > > rq->cfs.h_nr_queued = 0
> > > rq->rt.rt_nr_running = 1
> > >
> > > Although the waiting task was an RT task, "rq->next_class" did not point
> > > to rt_sched_class and was stuck at the lower priority fair_sched_class.
> >
> > Thank you all very much! I have started a short test. If all goes well
> > with the short tests, I will run an overnight test.
> >
> > I will of course let you know how the individual tests go.
>
> 20 minutes of 39*TREE03 (13 hours) on x86 passed! 2 hours of 9*TREE03 (18
> hours) on arm64 in progress. Starting five hours of 200*TREE03 on x86.
> If that works, 12 hours of same.
And the 2 hours of 9*TREE03 on arm64 passed! Started a 15-hour run.
Thanx, Paul
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [BUG] Apparent lost wakeups
2026-10-13 12:49 [BUG] Apparent lost wakeups Joel Fernandes
@ 2026-10-13 14:00 ` K Prateek Nayak
0 siblings, 0 replies; 18+ messages in thread
From: K Prateek Nayak @ 2026-10-13 14:00 UTC (permalink / raw)
To: Joel Fernandes
Cc: Peter Zijlstra, Vincent Guittot, paulmck, Ingo Molnar,
Juri Lelli, Dietmar Eggemann, Steven Rostedt, Ben Segall,
Mel Gorman, Valentin Schneider, linux-kernel, kernel-team
Hello Joel,
On 10/13/2026 6:19 PM, Joel Fernandes wrote:
> Quick question - with that fix, is there a possibility of a live lock if the sched loop keeps restarting because another CPU kept toggling RT?
__schedule() only drops rq_lock for two activities:
1. Within sched class' balance callback: This is done from all classes
from donor's sched class to below. Once a task is found by the
balance callback, we break out of prev_balance() and jump straight
to pick so something is guaranteed to be there.
All balance callbacks use double_rq_lock() so we are guaranteed to
move the task from a source rq.
2. For newidle balance: When no tasks exists on rq, fair class will do
a newidle balance to try find a task and prevent the need to switch
to idle tasks.
If a fair task is found or woken up, the retry is limited to idle
class. If a higher priority task is woken up, we do a full re-pick
starting from top.
Now, in theory, there is a possibility where:
1. rq->donor is a high priority RT task that went to sleep.
2. Lower priority RT tasks (>= 2) wakeup on the same CPU during
newidle balance. A retry is triggered.
3. balance_rt() triggers a pull because the newly woken tasks have a
lower priority than rq->donor which went to sleep.
4. pull_rt_task() drops rq_lock during double_rq_lock_balance()
5. A different rq gets lock first and moves the RT tasks away.
6. double_rq_lock_balance() returns, finds no task to push, and
eventually __schedule() goes to pick.
7. Fair class' pick triggers newidle balance and jump to step 2.
I'm not sure if there is a safeguard for this case where the CPU
can get stuck looping in pick_next_task() but chances of triggering
it is small (albeit not zero). This can happen even without the
changes from this patch.
Maybe I'm missing something and Peter can enlighten why this is
all impossible :-)
--
Thanks and Regards,
Prateek
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [BUG] Apparent lost wakeups
@ 2026-10-13 12:49 Joel Fernandes
2026-10-13 14:00 ` K Prateek Nayak
0 siblings, 1 reply; 18+ messages in thread
From: Joel Fernandes @ 2026-10-13 12:49 UTC (permalink / raw)
To: K Prateek Nayak
Cc: Peter Zijlstra, Vincent Guittot, paulmck, Ingo Molnar,
Juri Lelli, Dietmar Eggemann, Steven Rostedt, Ben Segall,
Mel Gorman, Valentin Schneider, linux-kernel, kernel-team
> On Oct 13, 2026, at 6:44 AM, K Prateek Nayak <kprateek.nayak@amd.com> wrote:
>
> On 10/13/2026 4:07 PM, Peter Zijlstra wrote:
>> Can you turn all this into a nice patch -- you've done all the hard work
>> on it anyway.
>
> Sure! I'll run TREE01 for sanity on my end and send the patch out
> later in the evening. Should give Paul some time to test and confirm
> that we are seeing the same issue :-)
So cool Prateek and Peter for figuring this out. Kudos!
Quick question - with that fix, is there a possibility of a live lock if the sched loop keeps restarting because another CPU kept toggling RT?
Looking forward to it,
thanks,
Joel Fernandes
>
> --
> Thanks and Regards,
> Prateek
>
^ permalink raw reply [flat|nested] 18+ messages in thread
end of thread, other threads:[~2026-10-13 20:06 UTC | newest]
Thread overview: 18+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-12 18:41 [BUG] Apparent lost wakeups Paul E. McKenney
2026-10-13 6:06 ` K Prateek Nayak
2026-10-13 6:46 ` K Prateek Nayak
2026-10-13 7:58 ` Vincent Guittot
2026-10-13 8:30 ` K Prateek Nayak
2026-10-13 8:36 ` Vincent Guittot
2026-10-13 10:14 ` Peter Zijlstra
2026-10-13 10:29 ` K Prateek Nayak
2026-10-13 10:37 ` Peter Zijlstra
2026-10-13 10:43 ` K Prateek Nayak
2026-10-13 10:06 ` Peter Zijlstra
2026-10-13 15:24 ` [PATCH] sched/core: Call wakeup_preempt() for queued tasks during class change K Prateek Nayak
2026-10-13 16:45 ` Vincent Guittot
2026-10-13 17:57 ` Paul E. McKenney
2026-10-13 19:55 ` Paul E. McKenney
2026-10-13 20:06 ` Paul E. McKenney
2026-10-13 12:49 [BUG] Apparent lost wakeups Joel Fernandes
2026-10-13 14:00 ` K Prateek Nayak
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®