* [PATCH v3 00/14] rcu: fix stuck defer_qs_pending state, add rescue timer and torture tests
@ 2026-06-18 18:50 Joel Fernandes
2026-06-18 18:50 ` [PATCH v3 01/14] rcu: introduce rcu_defer_qs_clear() helper Joel Fernandes
` (13 more replies)
0 siblings, 14 replies; 15+ messages in thread
From: Joel Fernandes @ 2026-06-18 18:50 UTC (permalink / raw)
To: linux-kernel
Cc: Paul E . McKenney, Frederic Weisbecker, Neeraj Upadhyay,
Josh Triplett, Boqun Feng, Uladzislau Rezki, Steven Rostedt,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Davidlohr Bueso,
Joel Fernandes, rcu
This series fixes a bug where rdp->defer_qs_pending can remain stuck in
PENDING when a preempted reader's quiescent state is reported up-tree via
a path other than the deferred-QS irq-work handler (FQS scan, hotplug
transition, expedited GP IPI, context switch). Once stuck, the pending
gate in rcu_read_unlock_special() silently suppresses all future arming
attempts on that CPU. The series adds PENDING -> IDLE transitions at the
missing sites (patches 1-7), including the case where the deferred-QS
irq-work handler may run between segments of a compound section (per Paul
McKenney's counter-example) and the softirq deferred-QS arming path.
Patch 8 adds a per-CPU rescue hrtimer that bounds the worst-case
deferred-QS reporting latency: when the irq-work handler lands in a clean
(non-reader, non-compound) context it reports the quiescent state directly
via the new rcu_preempt_deferred_qs_try_report() helper, and the rescue timer
reuses the same helper so that, under preempt=none, the QS report is quick
without depending on the scheduler.
Patches 9-13 add rcutorture coverage for the reader-end deboost behavior
(three from Paul, two from me). These were previously posted on their own
as an RFC; they are folded in here so the fix and its test coverage can be
reviewed together.
The last patch is a debug-only detector (CONFIG_RCU_GP_CLEANUP_STALE_CHECK,
marked [TEST COMMIT], not for merge) -- applied alone on unmodified
mainline without the fixes it reliably fires a WARN within 5 minutes under
TREE03 rcutorture, confirming the bug exists and the detector catches it;
with the full fix applied, I could not reproduce the issue.
The git tree with all patches can be found at:
git://git.kernel.org/pub/scm/linux/kernel/git/jfern/linux.git (tag: rcu-dqs-stuck-v3-20260618)
Change log:
Changes from v2 to v3:
- Folded in the rcutorture "reader-end deboost testing" patches (three from
Paul, two from me), previously posted separately as an RFC, so the fix
and its test coverage can be reviewed together:
https://lore.kernel.org/all/20260616222622.2981876-1-joelagnelf@nvidia.com/
- New patch "rcu: add per-CPU rescue hrtimer for deferred-QS reporting" to
bound the worst-case deferred-QS reporting latency.
- New patch "rcu: clear defer_qs_pending in deferred-QS bail when nesting > 0".
- Reworked "rcu: clear defer_qs_pending in handler for compounded sections":
the irq-work handler now reports the deferred QS directly via the new
rcu_preempt_deferred_qs_try_report() helper when it lands in a clean
context, instead of only nudging the scheduler.
Changes from v1 to v2:
- Dropped RFC tag now that softirq paths have been investigated.
- Added new patch "rcu: set need_resched on softirq deferred-QS arming
path" to handle the softirq arming case that was deferred in v1.
Link to v2: https://lore.kernel.org/all/20260526225014.314734-1-joelagnelf@nvidia.com/
Link to v1: https://lore.kernel.org/all/20260522142342.1536533-1-joelagnelf@nvidia.com/
Joel Fernandes (11):
rcu: introduce rcu_defer_qs_clear() helper
rcu: clear defer_qs_pending when notifying GP changes
rcu: clear defer_qs_pending in handler for compounded sections
rcu: drop redundant defer_qs_pending clear in irqrestore handler
rcu: clear defer_qs_pending at expedited IPI entry
rcu: set need_resched on softirq deferred-QS arming path
rcu: clear defer_qs_pending in deferred-QS bail when nesting > 0
rcu: add per-CPU rescue hrtimer for deferred-QS reporting
rcutorture: tighten boost-WARN to exclude any implicit-reader context
rcutorture: give async deboost mechanisms up to 500us before WARN
[TEST COMMIT] rcu: detect stuck defer_qs_pending at GP cleanup
Paul E. McKenney (3):
rcutorture: Abstract reader-segment dump into
rcu_torture_dump_read_segs()
rcutorture: Check for immediate deboosting at reader end
rcutorture: Test RCU readers from hardware interrupt handlers
kernel/rcu/Kconfig.debug | 11 ++
kernel/rcu/rcu.h | 7 ++
kernel/rcu/rcutorture.c | 257 +++++++++++++++++++++++++++------------
kernel/rcu/tree.c | 50 ++++++++
kernel/rcu/tree.h | 14 +++
kernel/rcu/tree_exp.h | 6 +
kernel/rcu/tree_plugin.h | 169 ++++++++++++++++++++++---
7 files changed, 419 insertions(+), 95 deletions(-)
base-commit: 95c7d025cc8c3c6c41206e2a18332eb04878b7ef
--
2.34.1
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v3 01/14] rcu: introduce rcu_defer_qs_clear() helper
2026-06-18 18:50 [PATCH v3 00/14] rcu: fix stuck defer_qs_pending state, add rescue timer and torture tests Joel Fernandes
@ 2026-06-18 18:50 ` Joel Fernandes
2026-06-18 18:50 ` [PATCH v3 02/14] rcu: clear defer_qs_pending when notifying GP changes Joel Fernandes
` (12 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Joel Fernandes @ 2026-06-18 18:50 UTC (permalink / raw)
To: linux-kernel
Cc: Paul E . McKenney, Frederic Weisbecker, Neeraj Upadhyay,
Josh Triplett, Boqun Feng, Uladzislau Rezki, Steven Rostedt,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Davidlohr Bueso,
Joel Fernandes, rcu
Currently rdp->defer_qs_pending transitions from DEFER_QS_PENDING to
DEFER_QS_IDLE at two sites: rcu_preempt_deferred_qs_irqrestore() and
rcu_preempt_deferred_qs_handler() (depth>0 reset). Both write the
IDLE value directly.
Introduce a single inline helper rcu_defer_qs_clear() in tree.h and
route both sites through it. This becomes the single
PENDING->IDLE transition point for upcoming work.
Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
kernel/rcu/tree.h | 5 +++++
kernel/rcu/tree_plugin.h | 4 ++--
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/kernel/rcu/tree.h b/kernel/rcu/tree.h
index 7dfc57e9adb1..4069132f9d44 100644
--- a/kernel/rcu/tree.h
+++ b/kernel/rcu/tree.h
@@ -296,6 +296,11 @@ struct rcu_data {
int cpu;
};
+static inline void rcu_defer_qs_clear(struct rcu_data *rdp)
+{
+ WRITE_ONCE(rdp->defer_qs_pending, DEFER_QS_IDLE);
+}
+
/* Values for nocb_defer_wakeup field in struct rcu_data. */
#define RCU_NOCB_WAKE_NOT 0
#define RCU_NOCB_WAKE_BYPASS 1
diff --git a/kernel/rcu/tree_plugin.h b/kernel/rcu/tree_plugin.h
index 95ad967adcf3..8637f405cb47 100644
--- a/kernel/rcu/tree_plugin.h
+++ b/kernel/rcu/tree_plugin.h
@@ -488,7 +488,7 @@ rcu_preempt_deferred_qs_irqrestore(struct task_struct *t, unsigned long flags)
rdp = this_cpu_ptr(&rcu_data);
if (rdp->defer_qs_pending == DEFER_QS_PENDING)
- rdp->defer_qs_pending = DEFER_QS_IDLE;
+ rcu_defer_qs_clear(rdp);
/*
* If RCU core is waiting for this CPU to exit its critical section,
@@ -645,7 +645,7 @@ static void rcu_preempt_deferred_qs_handler(struct irq_work *iwp)
* 5. Deferred QS reporting does not happen.
*/
if (rcu_preempt_depth() > 0)
- WRITE_ONCE(rdp->defer_qs_pending, DEFER_QS_IDLE);
+ rcu_defer_qs_clear(rdp);
}
/*
--
2.34.1
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v3 02/14] rcu: clear defer_qs_pending when notifying GP changes
2026-06-18 18:50 [PATCH v3 00/14] rcu: fix stuck defer_qs_pending state, add rescue timer and torture tests Joel Fernandes
2026-06-18 18:50 ` [PATCH v3 01/14] rcu: introduce rcu_defer_qs_clear() helper Joel Fernandes
@ 2026-06-18 18:50 ` Joel Fernandes
2026-06-18 18:50 ` [PATCH v3 03/14] rcu: clear defer_qs_pending in handler for compounded sections Joel Fernandes
` (11 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Joel Fernandes @ 2026-06-18 18:50 UTC (permalink / raw)
To: linux-kernel
Cc: Paul E . McKenney, Frederic Weisbecker, Neeraj Upadhyay,
Josh Triplett, Boqun Feng, Uladzislau Rezki, Steven Rostedt,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Davidlohr Bueso,
Joel Fernandes, rcu
Prior to this commit, defer_qs_pending was an unbalanced flag:
rcu_read_unlock_special() set it to PENDING whenever a deferred-QS
mechanism was scheduled, but the clear paths did not cover every
up-tree quiescent-state reporting site. In those cases the flag stays
PENDING after the QS is reported, and rcu_read_unlock_special()'s
pending-gate then silently rejects all future arming attempts.
A test patch confirms TREE03 can have get into the problematic stuck
state very quickly (< 5 minutes).
Clear the flag in __note_gp_changes(), right after the nothing-to-do
early return. This is the natural per-CPU "GP transitioned, sync local
state" hook, called from the GP-kthread's rcu_gp_init()/rcu_gp_cleanup()
paths, and other GP advancement paths.
For dynticks-idle CPUs, they do not call __note_gp_changes(), but they
also do not arm new PENDING work (no readers running), and on wake-up,
note_gp_changes() is called before any new reader runs.
Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
kernel/rcu/tree.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/kernel/rcu/tree.c b/kernel/rcu/tree.c
index 55df6d37145e..d0816468ffee 100644
--- a/kernel/rcu/tree.c
+++ b/kernel/rcu/tree.c
@@ -1281,6 +1281,8 @@ static bool __note_gp_changes(struct rcu_node *rnp, struct rcu_data *rdp)
if (rdp->gp_seq == rnp->gp_seq)
return false; /* Nothing to do. */
+ rcu_defer_qs_clear(rdp);
+
/* Handle the ends of any preceding grace periods first. */
if (rcu_seq_completed_gp(rdp->gp_seq, rnp->gp_seq) ||
unlikely(rdp->gpwrap)) {
--
2.34.1
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v3 03/14] rcu: clear defer_qs_pending in handler for compounded sections
2026-06-18 18:50 [PATCH v3 00/14] rcu: fix stuck defer_qs_pending state, add rescue timer and torture tests Joel Fernandes
2026-06-18 18:50 ` [PATCH v3 01/14] rcu: introduce rcu_defer_qs_clear() helper Joel Fernandes
2026-06-18 18:50 ` [PATCH v3 02/14] rcu: clear defer_qs_pending when notifying GP changes Joel Fernandes
@ 2026-06-18 18:50 ` Joel Fernandes
2026-06-18 18:50 ` [PATCH v3 04/14] rcu: drop redundant defer_qs_pending clear in irqrestore handler Joel Fernandes
` (10 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Joel Fernandes @ 2026-06-18 18:50 UTC (permalink / raw)
To: linux-kernel
Cc: Paul E . McKenney, Frederic Weisbecker, Neeraj Upadhyay,
Josh Triplett, Boqun Feng, Uladzislau Rezki, Steven Rostedt,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Davidlohr Bueso,
Joel Fernandes, rcu
The deferred-QS irq-work handler previously cleared defer_qs_pending
only when the handler ran inside an active rcu_read_lock() critical
section (rcu_preempt_depth() > 0). Paul McKenney pointed out a common
multi-segment compound pattern where the handler fires between
segments and segment N+1's arming attempt is silently suppressed by
the rcu_read_unlock_special() pending-gate:
rcu_read_lock(); // segment 1 starts
// may be preempted/boosted here
local_irq_disable();
rcu_read_unlock(); // segment 1 ends; arms defer_qs_pending
preempt_disable();
local_irq_enable(); // handler MAY fire here: depth==0, but
// but preempt is disabled, so it cant
// nudge.
rcu_read_lock(); // segment 2 starts
preempt_enable();
local_irq_disable();
rcu_read_unlock(); // arming attempt suppressed incorrectly -- (1)
local_irq_enable();
Waiting for the next __note_gp_changes() clear is too slow for the
compound case, we need the deferred QS report sooner.
Therefore, make the irq_work handler clear defer_qs_pending whenever
rcu_in_compounded_section() is true so that (1) can do the arming.
In addition, introduce rcu_preempt_deferred_qs_try_report(), a small
helper that reports the deferred QS (and releases any RCU priority
boost) directly, but only from a clean, non-reader/compound context.
When the handler lands in such a clean context it now reports the QS
directly instead of merely nudging the scheduler: this makes the
irq_work robust under preempt=none / voluntary, where a
set_need_resched() nudge would not enter __schedule() at IRQ exit and
the QS would otherwise wait for the next tick. When still compounded,
the handler falls back to clearing defer_qs_pending as before. The
bounded-delay rescue hrtimer added in a later patch reuses this same
helper.
Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
kernel/rcu/tree_plugin.h | 46 ++++++++++++++++++++++++++++------------
1 file changed, 33 insertions(+), 13 deletions(-)
diff --git a/kernel/rcu/tree_plugin.h b/kernel/rcu/tree_plugin.h
index 8637f405cb47..9b167eaf8e0d 100644
--- a/kernel/rcu/tree_plugin.h
+++ b/kernel/rcu/tree_plugin.h
@@ -622,7 +622,32 @@ notrace void rcu_preempt_deferred_qs(struct task_struct *t)
}
/*
- * Minimal handler to give the scheduler a chance to re-evaluate.
+ * Report a deferred quiescent state but only from a safe context.
+ *
+ * Both callers (the irq_work handler and the bounded-delay rescue hrtimer)
+ * run in hardirq context, so preempt_count() always has the HARDIRQ bit set;
+ * the compound-section check below deliberately inspects only the
+ * PREEMPT_MASK | SOFTIRQ_MASK bits, which reflect the INTERRUPTED caller's
+ * state, not ours.
+ */
+static bool rcu_preempt_deferred_qs_try_report(struct task_struct *t)
+{
+ unsigned long flags;
+
+ if (rcu_preempt_depth() > 0 ||
+ (preempt_count() & (PREEMPT_MASK | SOFTIRQ_MASK)))
+ return false;
+
+ if (rcu_preempt_need_deferred_qs(t)) {
+ local_irq_save(flags);
+ rcu_preempt_deferred_qs_irqrestore(t, flags);
+ }
+ return true;
+}
+
+/*
+ * Minimal handler to give the scheduler a chance to re-evaluate, and to
+ * report the deferred QS directly when the handler lands in a clean context.
*/
static void rcu_preempt_deferred_qs_handler(struct irq_work *iwp)
{
@@ -632,19 +657,14 @@ static void rcu_preempt_deferred_qs_handler(struct irq_work *iwp)
rdp = container_of(iwp, struct rcu_data, defer_qs_iw);
/*
- * If the IRQ work handler happens to run in the middle of RCU read-side
- * critical section, it could be ineffective in getting the scheduler's
- * attention to report a deferred quiescent state (the whole point of the
- * IRQ work). For this reason, requeue the IRQ work.
- *
- * Basically, we want to avoid following situation:
- * 1. rcu_read_unlock() queues IRQ work (state -> DEFER_QS_PENDING)
- * 2. CPU enters new rcu_read_lock()
- * 3. IRQ work runs but cannot report QS due to rcu_preempt_depth() > 0
- * 4. rcu_read_unlock() does not re-queue work (state still PENDING)
- * 5. Deferred QS reporting does not happen.
+ * If the handler fired in a clean context, report the deferred QS
+ * directly. This makes the irq_work robust under preempt=none /
+ * voluntary, where the set_need_resched() nudge would not enter
+ * __schedule() at IRQ exit. Otherwise we are still inside a reader /
+ * compound section: just clear defer_qs_pending so the next
+ * rcu_read_unlock() can rearm.
*/
- if (rcu_preempt_depth() > 0)
+ if (!rcu_preempt_deferred_qs_try_report(current))
rcu_defer_qs_clear(rdp);
}
--
2.34.1
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v3 04/14] rcu: drop redundant defer_qs_pending clear in irqrestore handler
2026-06-18 18:50 [PATCH v3 00/14] rcu: fix stuck defer_qs_pending state, add rescue timer and torture tests Joel Fernandes
` (2 preceding siblings ...)
2026-06-18 18:50 ` [PATCH v3 03/14] rcu: clear defer_qs_pending in handler for compounded sections Joel Fernandes
@ 2026-06-18 18:50 ` Joel Fernandes
2026-06-18 18:50 ` [PATCH v3 05/14] rcu: clear defer_qs_pending at expedited IPI entry Joel Fernandes
` (9 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Joel Fernandes @ 2026-06-18 18:50 UTC (permalink / raw)
To: linux-kernel
Cc: Paul E . McKenney, Frederic Weisbecker, Neeraj Upadhyay,
Josh Triplett, Boqun Feng, Uladzislau Rezki, Steven Rostedt,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Davidlohr Bueso,
Joel Fernandes, rcu
With __note_gp_changes() now clearing defer_qs_pending at every
per-CPU GP advance, the per-irqrestore clear is redundant. Remove it.
Effect: PENDING now stays set from arming until the next per-CPU GP
advance, future arming attempts on the same CPU within the same GP are
gated by the rcu_read_unlock_special().
This serves both as an optimization (should not need new irq_work again
this GP - for the compounded section case, we detect and clear it there),
and reduces risks of recursion due to clearing too aggressively.
Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
kernel/rcu/tree_plugin.h | 2 --
1 file changed, 2 deletions(-)
diff --git a/kernel/rcu/tree_plugin.h b/kernel/rcu/tree_plugin.h
index 9b167eaf8e0d..d17c2d97ebcc 100644
--- a/kernel/rcu/tree_plugin.h
+++ b/kernel/rcu/tree_plugin.h
@@ -487,8 +487,6 @@ rcu_preempt_deferred_qs_irqrestore(struct task_struct *t, unsigned long flags)
union rcu_special special;
rdp = this_cpu_ptr(&rcu_data);
- if (rdp->defer_qs_pending == DEFER_QS_PENDING)
- rcu_defer_qs_clear(rdp);
/*
* If RCU core is waiting for this CPU to exit its critical section,
--
2.34.1
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v3 05/14] rcu: clear defer_qs_pending at expedited IPI entry
2026-06-18 18:50 [PATCH v3 00/14] rcu: fix stuck defer_qs_pending state, add rescue timer and torture tests Joel Fernandes
` (3 preceding siblings ...)
2026-06-18 18:50 ` [PATCH v3 04/14] rcu: drop redundant defer_qs_pending clear in irqrestore handler Joel Fernandes
@ 2026-06-18 18:50 ` Joel Fernandes
2026-06-18 18:50 ` [PATCH v3 06/14] rcu: set need_resched on softirq deferred-QS arming path Joel Fernandes
` (8 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Joel Fernandes @ 2026-06-18 18:50 UTC (permalink / raw)
To: linux-kernel
Cc: Paul E . McKenney, Frederic Weisbecker, Neeraj Upadhyay,
Josh Triplett, Boqun Feng, Uladzislau Rezki, Steven Rostedt,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Davidlohr Bueso,
Joel Fernandes, rcu
The per-CPU clear in __note_gp_changes() (from an earlier commit)
runs only when the local CPU notices a normal-GP advance. When an
expedited GP arrives at a CPU whose defer_qs_pending is already
PENDING, rcu_read_unlock_special() may skip irq_work queuing due to the
pending gate.
Clear defer_qs_pending on the IPI target right in rcu_exp_handler().
This makes it possible for any arming attempt that follows
the IPI within the current GP to be able to queue irq_work again,
allowing completion expedited GPs quickly than waiting for one scheduler
tick.
Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
kernel/rcu/tree_exp.h | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/kernel/rcu/tree_exp.h b/kernel/rcu/tree_exp.h
index 82cada459e5d..f8564a041879 100644
--- a/kernel/rcu/tree_exp.h
+++ b/kernel/rcu/tree_exp.h
@@ -763,6 +763,12 @@ static void rcu_exp_handler(void *unused)
READ_ONCE(rdp->cpu_no_qs.b.exp)))
return;
+ /*
+ * Clear defer_qs_pending so arming attempts following this IPI
+ * within the current GP can queue irq_work again.
+ */
+ rcu_defer_qs_clear(rdp);
+
/*
* Second, the common case of not being in an RCU read-side
* critical section. If also enabled or idle, immediately
--
2.34.1
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v3 06/14] rcu: set need_resched on softirq deferred-QS arming path
2026-06-18 18:50 [PATCH v3 00/14] rcu: fix stuck defer_qs_pending state, add rescue timer and torture tests Joel Fernandes
` (4 preceding siblings ...)
2026-06-18 18:50 ` [PATCH v3 05/14] rcu: clear defer_qs_pending at expedited IPI entry Joel Fernandes
@ 2026-06-18 18:50 ` Joel Fernandes
2026-06-18 18:50 ` [PATCH v3 07/14] rcu: clear defer_qs_pending in deferred-QS bail when nesting > 0 Joel Fernandes
` (7 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Joel Fernandes @ 2026-06-18 18:50 UTC (permalink / raw)
To: linux-kernel
Cc: Paul E . McKenney, Frederic Weisbecker, Neeraj Upadhyay,
Josh Triplett, Boqun Feng, Uladzislau Rezki, Steven Rostedt,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Davidlohr Bueso,
Joel Fernandes, rcu
The arming code in rcu_read_unlock_special() has two paths for
deferring QS reporting: a softirq raise and an irq_work/set_need_resched
combination.
The irq_work path always calls set_need_resched_current() before
queuing irq_work. The softirq path does not, relying solely on the
softirq firing to do the rightthing.
This results in a problem as follows:
Consider 2 rcu_read_lock/unlock segments:
rcu_read_lock(); // segment 1 starts
// needs_exp becomes true
preempt_disable();
rcu_read_unlock(); // segment 1 ends; IRQs on, preempt
// off, needs_exp=true =>
// raise_softirq(RCU_SOFTIRQ);
// arms defer_qs_pending.
// Before this fix: no
// set_need_resched_current().
local_irq_disable();
preempt_enable(); // softirq pending but IRQs disabled
// hold it off.
rcu_read_lock(); // segment 2 starts
local_irq_enable(); // softirq fires: rcu_core runs, but
// we are inside a reader (depth>0)
// so no QS report; on softirq-exit
// preempt-check finds no
// need_resched -- still no nudge.
preempt_disable();
rcu_read_unlock(); // arming attempt suppressed
// incorrectly: defer_qs_pending
// already PENDING. Without this
// fix, no fresh
// set_need_resched_current() on
// this path either.
preempt_enable();
Therefore, add set_need_resched_current() to the softirq deferral path to
avoid long latencies in situations where GP needs to end sooner.
Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
kernel/rcu/tree_plugin.h | 1 +
1 file changed, 1 insertion(+)
diff --git a/kernel/rcu/tree_plugin.h b/kernel/rcu/tree_plugin.h
index d17c2d97ebcc..7045f0deee17 100644
--- a/kernel/rcu/tree_plugin.h
+++ b/kernel/rcu/tree_plugin.h
@@ -765,6 +765,7 @@ static void rcu_read_unlock_special(struct task_struct *t)
// Using softirq, safe to awaken, and either the
// wakeup is free or there is either an expedited
// GP in flight or a potential need to deboost.
+ set_need_resched_current();
if (rdp->defer_qs_pending != DEFER_QS_PENDING) {
rdp->defer_qs_pending = DEFER_QS_PENDING;
raise_softirq_irqoff(RCU_SOFTIRQ);
--
2.34.1
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v3 07/14] rcu: clear defer_qs_pending in deferred-QS bail when nesting > 0
2026-06-18 18:50 [PATCH v3 00/14] rcu: fix stuck defer_qs_pending state, add rescue timer and torture tests Joel Fernandes
` (5 preceding siblings ...)
2026-06-18 18:50 ` [PATCH v3 06/14] rcu: set need_resched on softirq deferred-QS arming path Joel Fernandes
@ 2026-06-18 18:50 ` Joel Fernandes
2026-06-18 18:50 ` [PATCH v3 08/14] rcu: add per-CPU rescue hrtimer for deferred-QS reporting Joel Fernandes
` (6 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Joel Fernandes @ 2026-06-18 18:50 UTC (permalink / raw)
To: linux-kernel
Cc: Paul E . McKenney, Frederic Weisbecker, Neeraj Upadhyay,
Josh Triplett, Boqun Feng, Uladzislau Rezki, Steven Rostedt,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Davidlohr Bueso,
Joel Fernandes, rcu
Paul McKenney noted that a softirq (or irq_work) handler arming for a
deferred QS can fire and find rcu_preempt_depth() > 0 -- the task is
still inside its outer reader, so rcu_preempt_need_deferred_qs() bails
without reporting the QS. At that point the queued mechanism has been
consumed but ->defer_qs_pending stays in DEFER_QS_PENDING.
In the meantime, the only remaining path back to a quiescent state on
this CPU may be a local_irq_disable()/_enable() pair that does not
call preempt_check_resched() (it is just `sti`/`cli`). patch 6's
unconditional set_need_resched_current() makes need_resched true, but
without an irq_work being raised the next outer rcu_read_unlock_special()
hits the P-gate at the arming code:
if (rdp->defer_qs_pending != DEFER_QS_PENDING) {
rdp->defer_qs_pending = DEFER_QS_PENDING;
irq_work_queue_on(...); // <-- skipped
}
so no irq_work is queued for the hardirq-exit preempt_schedule_irq()
path either. The deferred QS now waits until the next timer tick (or
similar preempt-safe boundary), needlessly extending expedited grace
period latency.
Clear ->defer_qs_pending in the bail-out path of rcu_preempt_deferred_qs()
when rcu_preempt_depth() > 0. The recursion guard semantics introduced
by commit b41642c87716 ("rcu: Fix rcu_read_unlock() deadloop due to IRQ
work").
The clear is also safe against fresh recursion at this exact program
point: rcu_preempt_depth() > 0 guarantees we are still inside an outer
reader, so any inner rcu_read_unlock() from tracing infrastructure
brings nesting back to outer (>0), never to 0. The slow path of
rcu_read_unlock_special() is structurally unreachable under that
condition, so no recursive raise_softirq_irqoff()/irq_work_queue_on()
can be triggered by the clear. Essentially, the mechanism will work to
prevent the following recursion which Xiongfeng had previously reported:
irq_exit() -> __irq_exit_rcu()
-> tick_irq_exit() -> tick_nohz_irq_exit() -> tick_nohz_stop_sched_tick()
-> trace_tick_stop() // BPF prog hooked here
-> rcu_read_unlock_special()
-> irq_work_queue_on(&rdp->defer_qs_iw, rdp->cpu) // self-IPI re-enters irq_exit
Reported-by: Paul E. McKenney <paulmck@kernel.org>
Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
kernel/rcu/tree_plugin.h | 28 +++++++++++++++++++++++++++-
1 file changed, 27 insertions(+), 1 deletion(-)
diff --git a/kernel/rcu/tree_plugin.h b/kernel/rcu/tree_plugin.h
index 7045f0deee17..960a45631098 100644
--- a/kernel/rcu/tree_plugin.h
+++ b/kernel/rcu/tree_plugin.h
@@ -612,9 +612,35 @@ static notrace bool rcu_preempt_need_deferred_qs(struct task_struct *t)
notrace void rcu_preempt_deferred_qs(struct task_struct *t)
{
unsigned long flags;
+ struct rcu_data *rdp;
- if (!rcu_preempt_need_deferred_qs(t))
+ if (!rcu_preempt_need_deferred_qs(t)) {
+ /*
+ * If we got here from a softirq/irq_work that fired while
+ * rcu_preempt_depth() > 0, the deferred-QS mechanism has been
+ * consumed without doing any work: rcu_preempt_need_deferred_qs()
+ * just returned false because the task is still in a reader, so
+ * the actual QS report has to wait for the next
+ * rcu_read_unlock().
+ *
+ * Clear ->defer_qs_pending here so the next outer
+ * rcu_read_unlock_special() can re-arm a fresh mechanism (in
+ * particular the irq_work path, which the local_irq_enable()
+ * recovery boundary cannot itself reschedule from).
+ *
+ * Recursion safety: rcu_preempt_depth() > 0 means we are inside
+ * an outer reader, so any inner rcu_read_unlock() reached via
+ * tracing (bpf programs attached to trace points) brings
+ * nesting to outer (> 0), never to 0, so no recursive
+ * raise_softirq_irqoff()/irq_work_queue_on() can be triggered
+ * by this clear.
+ */
+ if (rcu_preempt_depth() > 0) {
+ rdp = this_cpu_ptr(&rcu_data);
+ rcu_defer_qs_clear(rdp);
+ }
return;
+ }
local_irq_save(flags);
rcu_preempt_deferred_qs_irqrestore(t, flags);
}
--
2.34.1
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v3 08/14] rcu: add per-CPU rescue hrtimer for deferred-QS reporting
2026-06-18 18:50 [PATCH v3 00/14] rcu: fix stuck defer_qs_pending state, add rescue timer and torture tests Joel Fernandes
` (6 preceding siblings ...)
2026-06-18 18:50 ` [PATCH v3 07/14] rcu: clear defer_qs_pending in deferred-QS bail when nesting > 0 Joel Fernandes
@ 2026-06-18 18:50 ` Joel Fernandes
2026-06-18 18:50 ` [PATCH v3 09/14] rcutorture: Abstract reader-segment dump into rcu_torture_dump_read_segs() Joel Fernandes
` (5 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Joel Fernandes @ 2026-06-18 18:50 UTC (permalink / raw)
To: linux-kernel
Cc: Paul E . McKenney, Frederic Weisbecker, Neeraj Upadhyay,
Josh Triplett, Boqun Feng, Uladzislau Rezki, Steven Rostedt,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Davidlohr Bueso,
Joel Fernandes, rcu
The compound branch of rcu_read_unlock_special() arms either the
scheduler, RCU_SOFTIRQ (raise_softirq_irqoff) or irq_work_queue_on() inorder
to report a deferred QS at a later time.
However, that is not enough as in scenarios where local_irq_disable()d
sections span the preempt_enable() call of a preempt-disabled section:
rcu_read_lock();
// receive IPI for exp GP
preempt_disable();
rcu_read_unlock(); // Set the "need reschedule" flag.
local_irq_disable();
preempt_enable(); // Cannot reschedule as IRQs are off.
local_irq_enable();
// Now outside the compount RCU read-side critical section
// however, expedited GP is still help up.
Therefore, introduce a rescure timer, firing every 50 micro seconds
after the last rcu_read_unlock() call, to fix this.
Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
kernel/rcu/tree.c | 1 +
kernel/rcu/tree.h | 1 +
kernel/rcu/tree_plugin.h | 58 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 60 insertions(+)
diff --git a/kernel/rcu/tree.c b/kernel/rcu/tree.c
index d0816468ffee..8fd62775c176 100644
--- a/kernel/rcu/tree.c
+++ b/kernel/rcu/tree.c
@@ -27,6 +27,7 @@
#include <linux/sched.h>
#include <linux/sched/debug.h>
#include <linux/nmi.h>
+#include <linux/hrtimer.h>
#include <linux/atomic.h>
#include <linux/bitops.h>
#include <linux/export.h>
diff --git a/kernel/rcu/tree.h b/kernel/rcu/tree.h
index 4069132f9d44..3da43935f5e0 100644
--- a/kernel/rcu/tree.h
+++ b/kernel/rcu/tree.h
@@ -203,6 +203,7 @@ struct rcu_data {
/* during and after the last grace */
/* period it is aware of. */
struct irq_work defer_qs_iw; /* Obtain later scheduler attention. */
+ struct hrtimer defer_qs_iw_rescue;/* Rescue timer for deferred-QS. */
int defer_qs_pending; /* irqwork or softirq pending? */
struct work_struct strict_work; /* Schedule readers for strict GPs. */
diff --git a/kernel/rcu/tree_plugin.h b/kernel/rcu/tree_plugin.h
index 960a45631098..f906d5d59b7f 100644
--- a/kernel/rcu/tree_plugin.h
+++ b/kernel/rcu/tree_plugin.h
@@ -692,6 +692,54 @@ static void rcu_preempt_deferred_qs_handler(struct irq_work *iwp)
rcu_defer_qs_clear(rdp);
}
+/*
+ * Bounded-delay rescue timeout for the deferred-QS reporting.
+ *
+ * The compound branch of rcu_read_unlock_special() arms either the
+ * scheduler, RCU_SOFTIRQ (raise_softirq_irqoff) or irq_work_queue_on() inorder
+ * to report a deferred QS at a later time.
+ *
+ * However, that is not enough as in scenarios where local_irq_disable()d
+ * sections span the preempt_enable() call of a preempt-disabled section:
+ *
+ * rcu_read_lock();
+ * // receive IPI for exp GP
+ * preempt_disable();
+ * rcu_read_unlock(); // Set the "need reschedule" flag.
+ * local_irq_disable();
+ * preempt_enable(); // Cannot reschedule as IRQs are off.
+ * local_irq_enable();
+ * // Now outside the compount RCU read-side critical section
+ * // however, expedited GP is still help up.
+ *
+ * Introduce a rescue timer, firing every 50 micro seconds after the last
+ * rcu_read_unlock() call, to fix this.
+ */
+static int defer_qs_rescue_delay_us = 50;
+module_param(defer_qs_rescue_delay_us, int, 0644);
+MODULE_PARM_DESC(defer_qs_rescue_delay_us,
+ "Microseconds before the rescue timer fires a deferred-QS report.");
+
+static enum hrtimer_restart
+rcu_preempt_deferred_qs_rescue(struct hrtimer *hrtp)
+{
+ lockdep_assert_irqs_disabled();
+
+ /*
+ * Still inside a reader / compound section: deboosting is unsafe, so
+ * rearm and retry after a bounded delay. Once clean,
+ * rcu_preempt_deferred_qs_try_report() reports the deferred QS and
+ * releases any boost in the current task's context (or is a no-op if
+ * natural recovery already landed).
+ */
+ if (!rcu_preempt_deferred_qs_try_report(current)) {
+ hrtimer_forward_now(hrtp,
+ us_to_ktime(defer_qs_rescue_delay_us));
+ return HRTIMER_RESTART;
+ }
+ return HRTIMER_NORESTART;
+}
+
/*
* Check if expedited grace period processing during unlock is needed.
*
@@ -811,6 +859,13 @@ static void rcu_read_unlock_special(struct task_struct *t)
irq_work_queue_on(&rdp->defer_qs_iw, rdp->cpu);
}
}
+ // Bounded-delay rescue: arm whenever the compound branch
+ // entered with a pending deferred-QS / deboost obligation,
+ // regardless of which mechanism above was chosen.
+ if (needs_exp && cpu_online(rdp->cpu))
+ hrtimer_start(&rdp->defer_qs_iw_rescue,
+ us_to_ktime(defer_qs_rescue_delay_us),
+ HRTIMER_MODE_REL_PINNED_HARD);
local_irq_restore(flags);
return;
}
@@ -947,6 +1002,9 @@ dump_blkd_tasks(struct rcu_node *rnp, int ncheck)
static void rcu_preempt_deferred_qs_init(struct rcu_data *rdp)
{
rdp->defer_qs_iw = IRQ_WORK_INIT_HARD(rcu_preempt_deferred_qs_handler);
+ hrtimer_setup(&rdp->defer_qs_iw_rescue,
+ rcu_preempt_deferred_qs_rescue,
+ CLOCK_MONOTONIC, HRTIMER_MODE_REL_PINNED_HARD);
}
#else /* #ifdef CONFIG_PREEMPT_RCU */
--
2.34.1
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v3 09/14] rcutorture: Abstract reader-segment dump into rcu_torture_dump_read_segs()
2026-06-18 18:50 [PATCH v3 00/14] rcu: fix stuck defer_qs_pending state, add rescue timer and torture tests Joel Fernandes
` (7 preceding siblings ...)
2026-06-18 18:50 ` [PATCH v3 08/14] rcu: add per-CPU rescue hrtimer for deferred-QS reporting Joel Fernandes
@ 2026-06-18 18:50 ` Joel Fernandes
2026-06-18 18:50 ` [PATCH v3 10/14] rcutorture: Check for immediate deboosting at reader end Joel Fernandes
` (4 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Joel Fernandes @ 2026-06-18 18:50 UTC (permalink / raw)
To: linux-kernel
Cc: Paul E . McKenney, Frederic Weisbecker, Neeraj Upadhyay,
Josh Triplett, Boqun Feng, Uladzislau Rezki, Steven Rostedt,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Davidlohr Bueso,
Joel Fernandes, rcu
From: "Paul E. McKenney" <paulmck@kernel.org>
This commit abstracts the open-coded dumping of reader segments in the
rcu_torture_cleanup() function into a new rcu_torture_dump_read_segs()
function. This abstraction will allow reader segments to be dumped for
other purposes.
Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
kernel/rcu/rcutorture.c | 148 +++++++++++++++++++++-------------------
1 file changed, 76 insertions(+), 72 deletions(-)
diff --git a/kernel/rcu/rcutorture.c b/kernel/rcu/rcutorture.c
index 5f2848b828dc..42f94c62c18a 100644
--- a/kernel/rcu/rcutorture.c
+++ b/kernel/rcu/rcutorture.c
@@ -2378,6 +2378,80 @@ struct rcu_torture_one_read_state {
unsigned long long ts;
};
+static void rcu_torture_dump_read_segs(struct rt_read_seg *rrsp, int nsegs)
+{
+ bool firsttime;
+ int i;
+ int j;
+
+ firsttime = 1;
+ for (i = 0; i < nsegs; i++) {
+ if (IS_ENABLED(CONFIG_RCU_TORTURE_TEST_LOG_GP))
+ pr_alert("\t%lluus ", div64_u64(rrsp[i].rt_ts, 1000ULL));
+ else
+ pr_alert("\t");
+ pr_cont("%d: %#4x", i, rrsp[i].rt_readstate);
+ if (rrsp[i].rt_delay_jiffies != 0) {
+ pr_cont("%s%ldjiffies", firsttime ? "" : "+",
+ rrsp[i].rt_delay_jiffies);
+ firsttime = 0;
+ }
+ if (IS_ENABLED(CONFIG_RCU_TORTURE_TEST_LOG_CPU)) {
+ pr_cont(" CPU %2d", rrsp[i].rt_cpu);
+ if (rrsp[i].rt_cpu != rrsp[i].rt_end_cpu)
+ pr_cont("->%-2d", rrsp[i].rt_end_cpu);
+ else
+ pr_cont(" ...");
+ }
+ if (IS_ENABLED(CONFIG_RCU_TORTURE_TEST_LOG_GP) &&
+ cur_ops->gather_gp_seqs && cur_ops->format_gp_seqs) {
+ char buf1[20+1];
+ char buf2[20+1];
+ char sepchar = '-';
+
+ cur_ops->format_gp_seqs(rrsp[i].rt_gp_seq, buf1, ARRAY_SIZE(buf1));
+ cur_ops->format_gp_seqs(rrsp[i].rt_gp_seq_end, buf2, ARRAY_SIZE(buf2));
+ if (rrsp[i].rt_gp_seq == rrsp[i].rt_gp_seq_end) {
+ if (buf2[0]) {
+ for (j = 0; buf2[j]; j++)
+ buf2[j] = '.';
+ if (j)
+ buf2[j - 1] = ' ';
+ }
+ sepchar = ' ';
+ }
+ pr_cont(" %s%c%s", buf1, sepchar, buf2);
+ }
+ if (rrsp[i].rt_delay_ms != 0) {
+ pr_cont(" %s%ldms", firsttime ? "" : "+", rrsp[i].rt_delay_ms);
+ firsttime = 0;
+ }
+ if (rrsp[i].rt_delay_us != 0) {
+ pr_cont(" %s%ldus", firsttime ? "" : "+", rrsp[i].rt_delay_us);
+ firsttime = 0;
+ }
+ pr_cont("%s", rrsp[i].rt_preempted ? " preempted" : "");
+ if (rrsp[i].rt_readstate & RCUTORTURE_RDR_BH)
+ pr_cont(" BH");
+ if (rrsp[i].rt_readstate & RCUTORTURE_RDR_IRQ)
+ pr_cont(" IRQ");
+ if (rrsp[i].rt_readstate & RCUTORTURE_RDR_PREEMPT)
+ pr_cont(" PREEMPT");
+ if (rrsp[i].rt_readstate & RCUTORTURE_RDR_RBH)
+ pr_cont(" RBH");
+ if (rrsp[i].rt_readstate & RCUTORTURE_RDR_SCHED)
+ pr_cont(" SCHED");
+ if (rrsp[i].rt_readstate & RCUTORTURE_RDR_RCU_1)
+ pr_cont(" RCU_1");
+ if (rrsp[i].rt_readstate & RCUTORTURE_RDR_RCU_2)
+ pr_cont(" RCU_2");
+ pr_cont("\n");
+
+ }
+ if (rt_read_preempted)
+ pr_alert("\tReader was preempted.\n");
+}
+
static void init_rcu_torture_one_read_state(struct rcu_torture_one_read_state *rtorsp,
struct torture_random_state *trsp)
{
@@ -4071,11 +4145,9 @@ static void rcu_gpwrap_lag_cleanup(void)
static void
rcu_torture_cleanup(void)
{
- int firsttime;
int flags = 0;
unsigned long gp_seq = 0;
int i;
- int j;
if (torture_cleanup_begin()) {
if (cur_ops->cb_barrier != NULL) {
@@ -4160,76 +4232,8 @@ rcu_torture_cleanup(void)
pr_alert("Failure/close-call rcutorture reader segments:\n");
if (rt_read_nsegs == 0)
pr_alert("\t: No segments recorded!!!\n");
- firsttime = 1;
- for (i = 0; i < rt_read_nsegs; i++) {
- if (IS_ENABLED(CONFIG_RCU_TORTURE_TEST_LOG_GP))
- pr_alert("\t%lluus ", div64_u64(err_segs[i].rt_ts, 1000ULL));
- else
- pr_alert("\t");
- pr_cont("%d: %#4x", i, err_segs[i].rt_readstate);
- if (err_segs[i].rt_delay_jiffies != 0) {
- pr_cont("%s%ldjiffies", firsttime ? "" : "+",
- err_segs[i].rt_delay_jiffies);
- firsttime = 0;
- }
- if (IS_ENABLED(CONFIG_RCU_TORTURE_TEST_LOG_CPU)) {
- pr_cont(" CPU %2d", err_segs[i].rt_cpu);
- if (err_segs[i].rt_cpu != err_segs[i].rt_end_cpu)
- pr_cont("->%-2d", err_segs[i].rt_end_cpu);
- else
- pr_cont(" ...");
- }
- if (IS_ENABLED(CONFIG_RCU_TORTURE_TEST_LOG_GP) &&
- cur_ops->gather_gp_seqs && cur_ops->format_gp_seqs) {
- char buf1[20+1];
- char buf2[20+1];
- char sepchar = '-';
-
- cur_ops->format_gp_seqs(err_segs[i].rt_gp_seq,
- buf1, ARRAY_SIZE(buf1));
- cur_ops->format_gp_seqs(err_segs[i].rt_gp_seq_end,
- buf2, ARRAY_SIZE(buf2));
- if (err_segs[i].rt_gp_seq == err_segs[i].rt_gp_seq_end) {
- if (buf2[0]) {
- for (j = 0; buf2[j]; j++)
- buf2[j] = '.';
- if (j)
- buf2[j - 1] = ' ';
- }
- sepchar = ' ';
- }
- pr_cont(" %s%c%s", buf1, sepchar, buf2);
- }
- if (err_segs[i].rt_delay_ms != 0) {
- pr_cont(" %s%ldms", firsttime ? "" : "+",
- err_segs[i].rt_delay_ms);
- firsttime = 0;
- }
- if (err_segs[i].rt_delay_us != 0) {
- pr_cont(" %s%ldus", firsttime ? "" : "+",
- err_segs[i].rt_delay_us);
- firsttime = 0;
- }
- pr_cont("%s", err_segs[i].rt_preempted ? " preempted" : "");
- if (err_segs[i].rt_readstate & RCUTORTURE_RDR_BH)
- pr_cont(" BH");
- if (err_segs[i].rt_readstate & RCUTORTURE_RDR_IRQ)
- pr_cont(" IRQ");
- if (err_segs[i].rt_readstate & RCUTORTURE_RDR_PREEMPT)
- pr_cont(" PREEMPT");
- if (err_segs[i].rt_readstate & RCUTORTURE_RDR_RBH)
- pr_cont(" RBH");
- if (err_segs[i].rt_readstate & RCUTORTURE_RDR_SCHED)
- pr_cont(" SCHED");
- if (err_segs[i].rt_readstate & RCUTORTURE_RDR_RCU_1)
- pr_cont(" RCU_1");
- if (err_segs[i].rt_readstate & RCUTORTURE_RDR_RCU_2)
- pr_cont(" RCU_2");
- pr_cont("\n");
-
- }
- if (rt_read_preempted)
- pr_alert("\tReader was preempted.\n");
+ else
+ rcu_torture_dump_read_segs(err_segs, rt_read_nsegs);
}
if (atomic_read(&n_rcu_torture_error) || n_rcu_torture_barrier_error)
rcu_torture_print_module_parms(cur_ops, "End of test: FAILURE");
--
2.34.1
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v3 10/14] rcutorture: Check for immediate deboosting at reader end
2026-06-18 18:50 [PATCH v3 00/14] rcu: fix stuck defer_qs_pending state, add rescue timer and torture tests Joel Fernandes
` (8 preceding siblings ...)
2026-06-18 18:50 ` [PATCH v3 09/14] rcutorture: Abstract reader-segment dump into rcu_torture_dump_read_segs() Joel Fernandes
@ 2026-06-18 18:50 ` Joel Fernandes
2026-06-18 18:50 ` [PATCH v3 11/14] rcutorture: Test RCU readers from hardware interrupt handlers Joel Fernandes
` (3 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Joel Fernandes @ 2026-06-18 18:50 UTC (permalink / raw)
To: linux-kernel
Cc: Paul E . McKenney, Frederic Weisbecker, Neeraj Upadhyay,
Josh Triplett, Boqun Feng, Uladzislau Rezki, Steven Rostedt,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Davidlohr Bueso,
Joel Fernandes, rcu
From: "Paul E. McKenney" <paulmck@kernel.org>
This commit adds a check for failure to have fully deboosted a
multi-segmented RCU reader at the end of the full read-side critical
section. This check only happens for fully task-level readers, because a
a handler might have interrupted an already-boosted task-level RCU reader,
and a reader in that handler could then cause false positives. The first
failed check (due to an RCU reader that was not immediately deboosted)
causes a splat and a list of the segments making up that RCU reader.
Subsequent failures fail silently, all in the name of keeping console
output down to a dull roar.
Although most uses of RCU priority boosting serve as debugging aids,
this might change, and in fact might already have changed. And allowing
(for example) RCU priority boosting to persist until the next scheduler
tick could cause an aggressively real-time system to miss sub-millisecond
deadlines. So we do need to find this sort of problem during testing,
and preferably not in the field.
The name and type of the newly added rcu_torture_ops function pointer
(named "->is_task_rcu_boosted()") may need to change should other
end-of-reader checks be needed. But let's start simple.
Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
kernel/rcu/rcu.h | 7 +++++++
kernel/rcu/rcutorture.c | 22 ++++++++++++++++++++++
kernel/rcu/tree_plugin.h | 32 +++++++++++++++++++++++++++++++-
3 files changed, 60 insertions(+), 1 deletion(-)
diff --git a/kernel/rcu/rcu.h b/kernel/rcu/rcu.h
index fa6d30ce73d1..14faa11ef23c 100644
--- a/kernel/rcu/rcu.h
+++ b/kernel/rcu/rcu.h
@@ -695,4 +695,11 @@ static inline int rcu_stall_notifier_call_chain(unsigned long val, void *v) { re
void synchronize_rcu_trivial_preempt(void);
#endif // #ifdef CONFIG_TRIVIAL_PREEMPT_RCU
+#if defined(CONFIG_RCU_TORTURE_TEST) && defined(CONFIG_RCU_BOOST)
+bool rcu_is_task_rcu_boosted(void);
+#else // #if defined(CONFIG_RCU_TORTURE_TEST) && defined(CONFIG_RCU_BOOST)
+static inline bool rcu_is_task_rcu_boosted(void) { return false; }
+#endif // #else // #if defined(CONFIG_RCU_TORTURE_TEST) && defined(CONFIG_RCU_BOOST)
+
+
#endif /* __LINUX_RCU_H */
diff --git a/kernel/rcu/rcutorture.c b/kernel/rcu/rcutorture.c
index 42f94c62c18a..04c36fcde5cd 100644
--- a/kernel/rcu/rcutorture.c
+++ b/kernel/rcu/rcutorture.c
@@ -426,6 +426,7 @@ struct rcu_torture_ops {
void (*format_gp_seqs)(unsigned long long seqs, char *cp, size_t len);
void (*set_gpwrap_lag)(unsigned long lag);
int (*get_gpwrap_count)(int cpu);
+ bool (*is_task_rcu_boosted)(void);
long cbflood_max;
int irq_capable;
int can_boost;
@@ -635,6 +636,7 @@ static struct rcu_torture_ops rcu_ops = {
.format_gp_seqs = rcutorture_format_gp_seqs,
.set_gpwrap_lag = rcu_set_gpwrap_lag,
.get_gpwrap_count = rcu_get_gpwrap_count,
+ .is_task_rcu_boosted = rcu_is_task_rcu_boosted,
.irq_capable = 1,
.can_boost = IS_ENABLED(CONFIG_RCU_BOOST),
.extendables = RCUTORTURE_MAX_EXTEND,
@@ -2565,7 +2567,9 @@ static void rcu_torture_one_read_end(struct rcu_torture_one_read_state *rtorsp,
*/
static bool rcu_torture_one_read(struct torture_random_state *trsp, long myid)
{
+ static int firsttime = 1;
int newstate;
+ unsigned int nsegs;
struct rcu_torture_one_read_state rtors;
WARN_ON_ONCE(!rcu_is_watching());
@@ -2577,6 +2581,24 @@ static bool rcu_torture_one_read(struct torture_random_state *trsp, long myid)
return false;
rtors.rtrsp = rcutorture_loop_extend(&rtors.readstate, trsp, rtors.rtrsp);
rcu_torture_one_read_end(&rtors, trsp);
+ // This splat will happen on systems built with CONFIG_IRQ_WORK=n
+ // and on systems where arch_irq_work_has_interrupt() returns false.
+ // It might also happen on systems using a short-duration clock
+ // interrupt instead of a self-IPI (powerpc, s390) or that use
+ // neither a self-IPI nor a short-duration clock interrupts
+ // (all architectures using the generic implementation
+ // of arch_irq_work_raise()). On such systems, RCU cannot
+ // guarantee to immediately deboost RCU readers when the outermost
+ // rcu_read_unlock() does not end the full segmented RCU read-side
+ // critical section.
+ if (WARN_ON_ONCE(cur_ops->is_task_rcu_boosted && cur_ops->is_task_rcu_boosted() &&
+ !in_serving_softirq() && !in_hardirq() && !in_nmi()) &&
+ READ_ONCE(firsttime) && xchg(&firsttime, 0)) {
+ nsegs = rtors.rtrsp - rtors.rtseg;
+ nsegs = clamp_val(nsegs, 0, RCUTORTURE_RDR_MAX_SEGS);
+ pr_alert("Slow-deboost rcutorture reader segments:\n");
+ rcu_torture_dump_read_segs(rtors.rtseg, nsegs);
+ }
return true;
}
diff --git a/kernel/rcu/tree_plugin.h b/kernel/rcu/tree_plugin.h
index f906d5d59b7f..35d90eff871e 100644
--- a/kernel/rcu/tree_plugin.h
+++ b/kernel/rcu/tree_plugin.h
@@ -1026,7 +1026,7 @@ void rcu_read_unlock_strict(void)
*
* The in_atomic_preempt_off() check ensures that we come here holding
* the last preempt_count (which will get dropped once we return to
- * __rcu_read_unlock().
+ * __rcu_read_unlock()).
*/
rdp = this_cpu_ptr(&rcu_data);
rdp->cpu_no_qs.b.norm = false;
@@ -1423,6 +1423,36 @@ static void rcu_spawn_one_boost_kthread(struct rcu_node *rnp)
wake_up_process(t); /* get to TASK_INTERRUPTIBLE quickly. */
}
+#ifdef CONFIG_RCU_TORTURE_TEST
+
+/*
+ * Is the current task RCU priority boosted? This is used by
+ * rcutorture to check that tasks are always deboosted once then exit
+ * an RCU read-side critical section, no matter how many overlapping
+ * segments of rcu_read_lock(), preempt_disable(), local_bh_disable(),
+ * or local_irq_disable() made up that reader.
+ *
+ * The lockless accesses in rt_mutex_owner(&rnp->boost_mtx.rtmutex)
+ * are safe because tasks release ->boost_mtx when they own it, they
+ * cannot be boosted unless current->rcu_blocked_node is non-NULL,
+ * current->rcu_blocked_node is modified only by the current task,
+ * rt_mutex_owner() uses READ_ONCE() on the ->owner field, and the owner
+ * switching among other tasks cannot force an equality comparison.
+ */
+bool rcu_is_task_rcu_boosted(void)
+{
+ struct rcu_node *rnp;
+ struct task_struct *t = current;
+
+ rnp = t->rcu_blocked_node;
+ if (!rnp)
+ return false;
+ return rt_mutex_owner(&rnp->boost_mtx.rtmutex) == t;
+}
+EXPORT_SYMBOL_GPL(rcu_is_task_rcu_boosted);
+
+#endif // #ifdef CONFIG_RCU_TORTURE_TEST
+
#else /* #ifdef CONFIG_RCU_BOOST */
static void rcu_initiate_boost(struct rcu_node *rnp, unsigned long flags)
--
2.34.1
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v3 11/14] rcutorture: Test RCU readers from hardware interrupt handlers
2026-06-18 18:50 [PATCH v3 00/14] rcu: fix stuck defer_qs_pending state, add rescue timer and torture tests Joel Fernandes
` (9 preceding siblings ...)
2026-06-18 18:50 ` [PATCH v3 10/14] rcutorture: Check for immediate deboosting at reader end Joel Fernandes
@ 2026-06-18 18:50 ` Joel Fernandes
2026-06-18 18:50 ` [PATCH v3 12/14] rcutorture: tighten boost-WARN to exclude any implicit-reader context Joel Fernandes
` (2 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Joel Fernandes @ 2026-06-18 18:50 UTC (permalink / raw)
To: linux-kernel
Cc: Paul E . McKenney, Frederic Weisbecker, Neeraj Upadhyay,
Josh Triplett, Boqun Feng, Uladzislau Rezki, Steven Rostedt,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Davidlohr Bueso,
Joel Fernandes, rcu
From: "Paul E. McKenney" <paulmck@kernel.org>
Although rcutorture has long had the irqreader module parameter, this
parameter results only in RCU readers in softirq handlers, specifically,
timers. This commit therefore uses smp_call_function_single() to test
RCU readers in real hardware interrupt handlers, thus providing the full
effect from the irqreader module parameter.
However, consistency/debug checks must account for the possibility that
the smp_call_function_single() handler function is directly invoked
from the idle loop, in which case, for example, in_hardirq() will
return false. This commit uses a per-CPU variable to record being in
the rcu_torture_irq() smp_call_function_single() handler function.
Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
kernel/rcu/rcutorture.c | 75 ++++++++++++++++++++++++++++++++++++++---
1 file changed, 70 insertions(+), 5 deletions(-)
diff --git a/kernel/rcu/rcutorture.c b/kernel/rcu/rcutorture.c
index 04c36fcde5cd..13e0eec1e1ce 100644
--- a/kernel/rcu/rcutorture.c
+++ b/kernel/rcu/rcutorture.c
@@ -212,6 +212,7 @@ static long n_rcu_torture_boost_ktrerror;
static long n_rcu_torture_boost_failure;
static long n_rcu_torture_boosts;
static atomic_long_t n_rcu_torture_timers;
+static atomic_long_t n_rcu_torture_irqs;
static long n_barrier_attempts;
static long n_barrier_successes; /* did rcu_barrier test succeed? */
static unsigned long n_read_exits;
@@ -2103,6 +2104,8 @@ static void rcu_torture_reader_do_mbchk(long myid, struct rcu_torture *rtp,
smp_store_release(&rtrcp_assigner->rtc_chkrdr, -1); // Assigner can again assign.
}
+static DEFINE_PER_CPU(bool, torture_in_scf_handler);
+
// Verify the specified RCUTORTURE_RDR* state.
#define ROEC_ARGS "%s %s: Current %#x To add %#x To remove %#x preempt_count() %#x\n", __func__, s, curstate, new, old, preempt_count()
static void rcutorture_one_extend_check(char *s, int curstate, int new, int old)
@@ -2129,7 +2132,7 @@ static void rcutorture_one_extend_check(char *s, int curstate, int new, int old)
// Interrupt handlers have all sorts of stuff disabled, so ignore
// unintended disabling.
- if (in_serving_softirq() || in_hardirq())
+ if (in_serving_softirq() || in_hardirq() || this_cpu_read(torture_in_scf_handler))
return;
WARN_ONCE(cur_ops->extendables &&
@@ -2321,12 +2324,19 @@ rcutorture_extend_mask(int oldmask, struct torture_random_state *trsp)
mask |= RCUTORTURE_RDR_RCU_1;
}
+ /*
+ * Don't mess with interrupt masking in interrupt handlers.
+ */
+ if (in_hardirq() || this_cpu_read(torture_in_scf_handler))
+ mask &= ~(preempts_irq | bhs);
+
/*
* Can't enable bh w/irq disabled.
*/
if (mask & RCUTORTURE_RDR_IRQ)
mask |= oldmask & bhs;
+
/*
* Ideally these sequences would be detected in debug builds
* (regardless of RT), but until then don't stop testing
@@ -2581,6 +2591,7 @@ static bool rcu_torture_one_read(struct torture_random_state *trsp, long myid)
return false;
rtors.rtrsp = rcutorture_loop_extend(&rtors.readstate, trsp, rtors.rtrsp);
rcu_torture_one_read_end(&rtors, trsp);
+
// This splat will happen on systems built with CONFIG_IRQ_WORK=n
// and on systems where arch_irq_work_has_interrupt() returns false.
// It might also happen on systems using a short-duration clock
@@ -2618,7 +2629,7 @@ static void rcu_torture_timer(struct timer_list *unused)
atomic_long_inc(&n_rcu_torture_timers);
(void)rcu_torture_one_read(this_cpu_ptr(&rcu_torture_timer_rand), -1);
- /* Test call_rcu() invocation from interrupt handler. */
+ /* Test call_rcu() invocation from softirq handler. */
if (cur_ops->call) {
struct rcu_head *rhp = kmalloc_obj(*rhp, GFP_NOWAIT);
@@ -2627,6 +2638,41 @@ static void rcu_torture_timer(struct timer_list *unused)
}
}
+static DEFINE_TORTURE_RANDOM_PERCPU(rcu_torture_irq_rand);
+
+/*
+ * RCU torture reader from timer handler. Dereferences rcu_torture_current,
+ * incrementing the corresponding element of the pipeline array. The
+ * counter in the element should never be greater than 1, otherwise, the
+ * RCU implementation is broken.
+ *
+ * Note that on some systems, "interrupts" from idle are direct calls
+ * rather than interrupts. The torture_in_scf_handler per-CPU variable
+ * accounts for this case.
+ */
+static void rcu_torture_irq(void *unused)
+{
+ WARN_ON_ONCE(in_nmi());
+ lockdep_assert_irqs_disabled();
+ atomic_long_inc(&n_rcu_torture_irqs);
+ this_cpu_write(torture_in_scf_handler, true);
+ (void)rcu_torture_one_read(this_cpu_ptr(&rcu_torture_irq_rand), -1);
+ this_cpu_write(torture_in_scf_handler, false);
+
+ // Test call_rcu() invocation from interrupt handler. Interrupts
+ // will always be disabled here, even in CONFIG_PREEMPT_RT=y kernels.
+ // The "right" thing to do would be to create a special-purpose
+ // lockless or raw-spinlock-protected allocator, but in the meantime,
+ // skip testing call_rcu() from interrupt handlers in kernels built
+ // with either CONFIG_PREEMPT_RT=y or CONFIG_PROVE_LOCKING=y.
+ if (cur_ops->call && !IS_ENABLED(CONFIG_PROVE_LOCKING) && !IS_ENABLED(CONFIG_PREEMPT_RT)) {
+ struct rcu_head *rhp = kmalloc_obj(*rhp, GFP_NOWAIT);
+
+ if (rhp)
+ cur_ops->call(rhp, rcu_torture_timer_cb);
+ }
+}
+
/*
* RCU torture reader kthread. Repeatedly dereferences rcu_torture_current,
* incrementing the corresponding element of the pipeline array. The
@@ -2636,6 +2682,7 @@ static void rcu_torture_timer(struct timer_list *unused)
static int
rcu_torture_reader(void *arg)
{
+ unsigned long lastscf = jiffies;
unsigned long lastsleep = jiffies;
long myid = (long)arg;
int mynumonline = myid;
@@ -2649,8 +2696,25 @@ rcu_torture_reader(void *arg)
tick_dep_set_task(current, TICK_DEP_BIT_RCU); // CPU bound, so need tick.
do {
if (irqreader && cur_ops->irq_capable) {
- if (!timer_pending(&t))
+ if (!timer_pending(&t)) {
+ int cpu;
+
mod_timer(&t, jiffies + 1);
+ preempt_disable();
+ cpu = torture_random(&rand) % nr_cpu_ids;
+ if (!cpu_online(cpu)) {
+ cpu = cpumask_next(cpu, cpu_online_mask);
+ if (cpu >= nr_cpu_ids)
+ cpu = cpumask_next(-1, cpu_online_mask);
+ }
+ // An smp_call_function_single() to self is not an interrupt!
+ if (cpu != smp_processor_id() &&
+ time_after(jiffies, lastscf + HZ * nrealreaders / 50)) {
+ smp_call_function_single(cpu, rcu_torture_irq, NULL, 0);
+ lastscf = jiffies;
+ }
+ preempt_enable();
+ }
}
if (!rcu_torture_one_read(&rand, myid) && !torture_must_stop())
schedule_timeout_interruptible(HZ);
@@ -2926,10 +2990,11 @@ rcu_torture_stats_print(void)
atomic_read(&n_rcu_torture_mbchk_fail), atomic_read(&n_rcu_torture_mbchk_tries),
n_rcu_torture_barrier_error,
n_rcu_torture_boost_ktrerror);
- pr_cont("rtbf: %ld rtb: %ld nt: %ld ",
+ pr_cont("rtbf: %ld rtb: %ld nt: %ld ni: %ld ",
n_rcu_torture_boost_failure,
n_rcu_torture_boosts,
- atomic_long_read(&n_rcu_torture_timers));
+ atomic_long_read(&n_rcu_torture_timers),
+ atomic_long_read(&n_rcu_torture_irqs));
if (updownreaders)
pr_cont("ndowns: %lu nups: %lu nhrt: %lu nmigrates: %lu ", ndowns, nups, nunexpired, nmigrates);
torture_onoff_stats();
--
2.34.1
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v3 12/14] rcutorture: tighten boost-WARN to exclude any implicit-reader context
2026-06-18 18:50 [PATCH v3 00/14] rcu: fix stuck defer_qs_pending state, add rescue timer and torture tests Joel Fernandes
` (10 preceding siblings ...)
2026-06-18 18:50 ` [PATCH v3 11/14] rcutorture: Test RCU readers from hardware interrupt handlers Joel Fernandes
@ 2026-06-18 18:50 ` Joel Fernandes
2026-06-18 18:50 ` [PATCH v3 13/14] rcutorture: give async deboost mechanisms up to 500us before WARN Joel Fernandes
2026-06-18 18:50 ` [PATCH v3 14/14] [TEST COMMIT] rcu: detect stuck defer_qs_pending at GP cleanup Joel Fernandes
13 siblings, 0 replies; 15+ messages in thread
From: Joel Fernandes @ 2026-06-18 18:50 UTC (permalink / raw)
To: linux-kernel
Cc: Paul E . McKenney, Frederic Weisbecker, Neeraj Upadhyay,
Josh Triplett, Boqun Feng, Uladzislau Rezki, Steven Rostedt,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Davidlohr Bueso,
Joel Fernandes, rcu
The Slow-deboost WARN_ON_ONCE in rcu_torture_one_read() currently uses
only the handler-context predicates to decide whether the calling
context is "safe" to assert immediate deboosting:
!in_serving_softirq() && !in_hardirq() && !in_nmi()
They do NOT exclude several other contexts in which the task is still
holding an implicit RCU read-side critical section.
1. local_bh_disable()-but-not-in-softirq.
2. preempt_disable()-held.
3. irqs_disabled().
4. rcu_preempt_depth() > 0.
The current predicate set lets the WARN fire while these implicit
readers are still active, which is a false positive: the user-visible
"tardy deboost" finding becomes ambiguous between an actual
deferred-QS bug and a valid caller still inside an implicit-reader
section.
Replace the three handler-only predicates with the following which
covers everything.
preempt_count() == 0 && !irqs_disabled() && rcu_preempt_depth() == 0
Note that we only check in contexts where rcu_read_unlock_special() deboosts
synchronously, i.e. where it is not inside an implicit RCU read-side
critical section. This matches rcu_read_unlock_special()'s own gate
and rcu_flavor_sched_clock_irq()'s:
"rcu_preempt_depth() > 0 || (preempt_count() & (PREEMPT_MASK | SOFTIRQ_MASK))".
Also note, in PREEMPT_RT, softirqs always run under local_bh_disable(),
which itself takes rcu_read_lock() to satisfy RCU's bottom-half
requirement (see softirq.c) — so rcu_preempt_depth() is non-zero
throughout any RT softirq thus making in_serving_softirq() redundant.
Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
kernel/rcu/rcutorture.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/kernel/rcu/rcutorture.c b/kernel/rcu/rcutorture.c
index 13e0eec1e1ce..e0bea4039c42 100644
--- a/kernel/rcu/rcutorture.c
+++ b/kernel/rcu/rcutorture.c
@@ -2603,7 +2603,8 @@ static bool rcu_torture_one_read(struct torture_random_state *trsp, long myid)
// rcu_read_unlock() does not end the full segmented RCU read-side
// critical section.
if (WARN_ON_ONCE(cur_ops->is_task_rcu_boosted && cur_ops->is_task_rcu_boosted() &&
- !in_serving_softirq() && !in_hardirq() && !in_nmi()) &&
+ preempt_count() == 0 && !irqs_disabled() &&
+ rcu_preempt_depth() == 0) &&
READ_ONCE(firsttime) && xchg(&firsttime, 0)) {
nsegs = rtors.rtrsp - rtors.rtseg;
nsegs = clamp_val(nsegs, 0, RCUTORTURE_RDR_MAX_SEGS);
--
2.34.1
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v3 13/14] rcutorture: give async deboost mechanisms up to 500us before WARN
2026-06-18 18:50 [PATCH v3 00/14] rcu: fix stuck defer_qs_pending state, add rescue timer and torture tests Joel Fernandes
` (11 preceding siblings ...)
2026-06-18 18:50 ` [PATCH v3 12/14] rcutorture: tighten boost-WARN to exclude any implicit-reader context Joel Fernandes
@ 2026-06-18 18:50 ` Joel Fernandes
2026-06-18 18:50 ` [PATCH v3 14/14] [TEST COMMIT] rcu: detect stuck defer_qs_pending at GP cleanup Joel Fernandes
13 siblings, 0 replies; 15+ messages in thread
From: Joel Fernandes @ 2026-06-18 18:50 UTC (permalink / raw)
To: linux-kernel
Cc: Paul E . McKenney, Frederic Weisbecker, Neeraj Upadhyay,
Josh Triplett, Boqun Feng, Uladzislau Rezki, Steven Rostedt,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Davidlohr Bueso,
Joel Fernandes, rcu
The Slow-deboost detector in rcu_torture_one_read() checks for a still-
boosted task immediately after the outer rcu_read_unlock() returns.
Sub-microsecond checking is unrealistic for any async deboost mechanism
(irq_work, softirq dispatch). As, even with a delay in deboosting of
just 50us, the check beats it.
Therefore, poll the boost state for up to 500us before declaring the
deboost tardy.
Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
kernel/rcu/rcutorture.c | 29 ++++++++++++++++++++---------
1 file changed, 20 insertions(+), 9 deletions(-)
diff --git a/kernel/rcu/rcutorture.c b/kernel/rcu/rcutorture.c
index e0bea4039c42..12813b5381d6 100644
--- a/kernel/rcu/rcutorture.c
+++ b/kernel/rcu/rcutorture.c
@@ -2601,15 +2601,26 @@ static bool rcu_torture_one_read(struct torture_random_state *trsp, long myid)
// of arch_irq_work_raise()). On such systems, RCU cannot
// guarantee to immediately deboost RCU readers when the outermost
// rcu_read_unlock() does not end the full segmented RCU read-side
- // critical section.
- if (WARN_ON_ONCE(cur_ops->is_task_rcu_boosted && cur_ops->is_task_rcu_boosted() &&
- preempt_count() == 0 && !irqs_disabled() &&
- rcu_preempt_depth() == 0) &&
- READ_ONCE(firsttime) && xchg(&firsttime, 0)) {
- nsegs = rtors.rtrsp - rtors.rtseg;
- nsegs = clamp_val(nsegs, 0, RCUTORTURE_RDR_MAX_SEGS);
- pr_alert("Slow-deboost rcutorture reader segments:\n");
- rcu_torture_dump_read_segs(rtors.rtseg, nsegs);
+ // critical section. Allow up to 500us for an async deboost
+ // mechanism (e.g. rescue hrtimer, default 50us) to fire before
+ // declaring the deboost tardy.
+ if (cur_ops->is_task_rcu_boosted && cur_ops->is_task_rcu_boosted() &&
+ preempt_count() == 0 && !irqs_disabled() &&
+ rcu_preempt_depth() == 0) {
+ int wait_us;
+
+ for (wait_us = 0; wait_us < 500; wait_us += 10) {
+ udelay(10);
+ if (!cur_ops->is_task_rcu_boosted())
+ break;
+ }
+ if (WARN_ON_ONCE(cur_ops->is_task_rcu_boosted()) &&
+ READ_ONCE(firsttime) && xchg(&firsttime, 0)) {
+ nsegs = rtors.rtrsp - rtors.rtseg;
+ nsegs = clamp_val(nsegs, 0, RCUTORTURE_RDR_MAX_SEGS);
+ pr_alert("Slow-deboost rcutorture reader segments:\n");
+ rcu_torture_dump_read_segs(rtors.rtseg, nsegs);
+ }
}
return true;
}
--
2.34.1
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v3 14/14] [TEST COMMIT] rcu: detect stuck defer_qs_pending at GP cleanup
2026-06-18 18:50 [PATCH v3 00/14] rcu: fix stuck defer_qs_pending state, add rescue timer and torture tests Joel Fernandes
` (12 preceding siblings ...)
2026-06-18 18:50 ` [PATCH v3 13/14] rcutorture: give async deboost mechanisms up to 500us before WARN Joel Fernandes
@ 2026-06-18 18:50 ` Joel Fernandes
13 siblings, 0 replies; 15+ messages in thread
From: Joel Fernandes @ 2026-06-18 18:50 UTC (permalink / raw)
To: linux-kernel
Cc: Paul E . McKenney, Frederic Weisbecker, Neeraj Upadhyay,
Josh Triplett, Boqun Feng, Uladzislau Rezki, Steven Rostedt,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Davidlohr Bueso,
Joel Fernandes, rcu
Debug-only stuck-state detector for upstream review. This commit is
NOT for merge; it is a review aid. Reviewers can enable
CONFIG_RCU_GP_CLEANUP_STALE_CHECK to gain runtime confidence in the
preceding fix commits.
Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
kernel/rcu/Kconfig.debug | 11 ++++++++++
kernel/rcu/tree.c | 47 ++++++++++++++++++++++++++++++++++++++++
kernel/rcu/tree.h | 8 +++++++
3 files changed, 66 insertions(+)
diff --git a/kernel/rcu/Kconfig.debug b/kernel/rcu/Kconfig.debug
index 35218ba74eb5..5a40c4fe544c 100644
--- a/kernel/rcu/Kconfig.debug
+++ b/kernel/rcu/Kconfig.debug
@@ -98,6 +98,17 @@ config RCU_TORTURE_TEST_LOG_GP
Say Y here if you want grace-period sequence numbers logged.
Say N if you are unsure.
+config RCU_GP_CLEANUP_STALE_CHECK
+ bool "Detect stuck defer_qs_pending state at GP cleanup"
+ depends on RCU_TORTURE_TEST
+ default n
+ help
+ This option adds a per-CPU instrumentation counter on every
+ PENDING -> IDLE transition of rdp->defer_qs_pending, and a
+ detector in rcu_gp_cleanup().
+
+ Say N if you are unsure.
+
config RCU_REF_SCALE_TEST
tristate "Scalability tests for read-side synchronization (RCU and others)"
depends on DEBUG_KERNEL
diff --git a/kernel/rcu/tree.c b/kernel/rcu/tree.c
index 8fd62775c176..67d51990427d 100644
--- a/kernel/rcu/tree.c
+++ b/kernel/rcu/tree.c
@@ -2147,6 +2147,52 @@ static noinline_for_stack void rcu_gp_fqs_loop(void)
}
}
+#ifdef CONFIG_RCU_GP_CLEANUP_STALE_CHECK
+/*
+ * Threshold of consecutive GPs with rdp->defer_qs_pending stuck at
+ * PENDING and no observed PENDING -> IDLE transition before WARN.
+ */
+#define RCU_DEFER_QS_STUCK_GPS_THRESHOLD 5
+
+static void rcu_gp_cleanup_stale_check(void)
+{
+ int cpu;
+ unsigned long cur_gp_seq = READ_ONCE(rcu_state.gp_seq);
+
+ for_each_online_cpu(cpu) {
+ struct rcu_data *rdp = per_cpu_ptr(&rcu_data, cpu);
+ s64 clears_now;
+ int p_now;
+
+ if (READ_ONCE(rdp->gp_seq) != cur_gp_seq) {
+ rdp->defer_qs_pending_stuck_gps = 0;
+ rdp->defer_qs_pending_clears_snap =
+ atomic64_read(&rdp->defer_qs_pending_clears);
+ continue;
+ }
+
+ clears_now = atomic64_read(&rdp->defer_qs_pending_clears);
+ p_now = READ_ONCE(rdp->defer_qs_pending);
+
+ if (p_now != DEFER_QS_PENDING ||
+ clears_now != rdp->defer_qs_pending_clears_snap) {
+ rdp->defer_qs_pending_stuck_gps = 0;
+ rdp->defer_qs_pending_clears_snap = clears_now;
+ continue;
+ }
+
+ rdp->defer_qs_pending_stuck_gps++;
+ WARN_ONCE(rdp->defer_qs_pending_stuck_gps >=
+ RCU_DEFER_QS_STUCK_GPS_THRESHOLD,
+ "RCU: defer_qs_pending STUCK on CPU %d for %u GPs (gp_seq=%lu, clears=%lld)\n",
+ cpu, rdp->defer_qs_pending_stuck_gps,
+ cur_gp_seq, clears_now);
+ }
+}
+#else
+static inline void rcu_gp_cleanup_stale_check(void) { }
+#endif /* CONFIG_RCU_GP_CLEANUP_STALE_CHECK */
+
/*
* Clean up after the old grace period.
*/
@@ -2221,6 +2267,7 @@ static noinline void rcu_gp_cleanup(void)
/* Declare grace period done, trace first to use old GP number. */
trace_rcu_grace_period(rcu_state.name, rcu_state.gp_seq, TPS("end"));
+ rcu_gp_cleanup_stale_check();
rcu_seq_end(&rcu_state.gp_seq);
ASSERT_EXCLUSIVE_WRITER(rcu_state.gp_seq);
WRITE_ONCE(rcu_state.gp_state, RCU_GP_IDLE);
diff --git a/kernel/rcu/tree.h b/kernel/rcu/tree.h
index 3da43935f5e0..010052a55477 100644
--- a/kernel/rcu/tree.h
+++ b/kernel/rcu/tree.h
@@ -205,6 +205,11 @@ struct rcu_data {
struct irq_work defer_qs_iw; /* Obtain later scheduler attention. */
struct hrtimer defer_qs_iw_rescue;/* Rescue timer for deferred-QS. */
int defer_qs_pending; /* irqwork or softirq pending? */
+#ifdef CONFIG_RCU_GP_CLEANUP_STALE_CHECK
+ atomic64_t defer_qs_pending_clears;
+ s64 defer_qs_pending_clears_snap;
+ unsigned int defer_qs_pending_stuck_gps;
+#endif
struct work_struct strict_work; /* Schedule readers for strict GPs. */
/* 2) batch handling */
@@ -300,6 +305,9 @@ struct rcu_data {
static inline void rcu_defer_qs_clear(struct rcu_data *rdp)
{
WRITE_ONCE(rdp->defer_qs_pending, DEFER_QS_IDLE);
+#ifdef CONFIG_RCU_GP_CLEANUP_STALE_CHECK
+ atomic64_inc(&rdp->defer_qs_pending_clears);
+#endif
}
/* Values for nocb_defer_wakeup field in struct rcu_data. */
--
2.34.1
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-06-18 18:51 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-06-18 18:50 [PATCH v3 00/14] rcu: fix stuck defer_qs_pending state, add rescue timer and torture tests Joel Fernandes
2026-06-18 18:50 ` [PATCH v3 01/14] rcu: introduce rcu_defer_qs_clear() helper Joel Fernandes
2026-06-18 18:50 ` [PATCH v3 02/14] rcu: clear defer_qs_pending when notifying GP changes Joel Fernandes
2026-06-18 18:50 ` [PATCH v3 03/14] rcu: clear defer_qs_pending in handler for compounded sections Joel Fernandes
2026-06-18 18:50 ` [PATCH v3 04/14] rcu: drop redundant defer_qs_pending clear in irqrestore handler Joel Fernandes
2026-06-18 18:50 ` [PATCH v3 05/14] rcu: clear defer_qs_pending at expedited IPI entry Joel Fernandes
2026-06-18 18:50 ` [PATCH v3 06/14] rcu: set need_resched on softirq deferred-QS arming path Joel Fernandes
2026-06-18 18:50 ` [PATCH v3 07/14] rcu: clear defer_qs_pending in deferred-QS bail when nesting > 0 Joel Fernandes
2026-06-18 18:50 ` [PATCH v3 08/14] rcu: add per-CPU rescue hrtimer for deferred-QS reporting Joel Fernandes
2026-06-18 18:50 ` [PATCH v3 09/14] rcutorture: Abstract reader-segment dump into rcu_torture_dump_read_segs() Joel Fernandes
2026-06-18 18:50 ` [PATCH v3 10/14] rcutorture: Check for immediate deboosting at reader end Joel Fernandes
2026-06-18 18:50 ` [PATCH v3 11/14] rcutorture: Test RCU readers from hardware interrupt handlers Joel Fernandes
2026-06-18 18:50 ` [PATCH v3 12/14] rcutorture: tighten boost-WARN to exclude any implicit-reader context Joel Fernandes
2026-06-18 18:50 ` [PATCH v3 13/14] rcutorture: give async deboost mechanisms up to 500us before WARN Joel Fernandes
2026-06-18 18:50 ` [PATCH v3 14/14] [TEST COMMIT] rcu: detect stuck defer_qs_pending at GP cleanup Joel Fernandes
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox