[PATCH v2 07/10] srcu: Detect duplicate callback enqueue at registration
From: Kunwu Chan
Date: Tue Oct 27 2026 - 03:11:34 EST
Move the duplicate-callback check from srcu_do_enqueue() to
__call_srcu() entry so that detected duplicate registrations
are rejected before they reach the deferred-list enqueue path.
Use __ratelimit() to limit the diagnostic output (error message,
object dump, and stack trace) as a group.
Suggested-by: Paul E. McKenney <paulmck@xxxxxxxxxx>
Signed-off-by: Kunwu Chan <chentao@xxxxxxxxxx>
---
kernel/rcu/srcutree.c | 24 ++++++++++++++++++------
1 file changed, 18 insertions(+), 6 deletions(-)
diff --git a/kernel/rcu/srcutree.c b/kernel/rcu/srcutree.c
index 1993ddbdb828..81423b3dffae 100644
--- a/kernel/rcu/srcutree.c
+++ b/kernel/rcu/srcutree.c
@@ -23,10 +23,12 @@
#include <linux/kasan.h>
#include <linux/llist.h>
#include <linux/rcupdate_wait.h>
+#include <linux/ratelimit.h>
#include <linux/sched.h>
#include <linux/smp.h>
#include <linux/delay.h>
#include <linux/module.h>
+#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/srcu.h>
@@ -1511,12 +1513,6 @@ static unsigned long srcu_gp_start_if_needed(struct srcu_struct *ssp,
static void srcu_do_enqueue(struct srcu_struct *ssp, struct rcu_head *rhp,
rcu_callback_t func, bool do_norm)
{
- if (debug_rcu_head_queue(rhp)) {
- /* Probable double call_srcu(), so leak the callback. */
- WRITE_ONCE(rhp->func, srcu_leak_callback);
- WARN_ONCE(1, "call_srcu(): Leaked duplicate callback\n");
- return;
- }
rhp->func = func;
(void)srcu_gp_start_if_needed(ssp, rhp, do_norm);
}
@@ -1526,6 +1522,10 @@ static void srcu_do_enqueue(struct srcu_struct *ssp, struct rcu_head *rhp,
* disabled, so defer when interrupts are already off rather than enqueue into
* an operation that may be in flight on this CPU.
*/
+static DEFINE_RATELIMIT_STATE(rcu_srcu_dup_rs,
+ DEFAULT_RATELIMIT_INTERVAL,
+ DEFAULT_RATELIMIT_BURST);
+
static void __call_srcu(struct srcu_struct *ssp, struct rcu_head *rhp,
rcu_callback_t func, bool do_norm)
{
@@ -1539,6 +1539,17 @@ static void __call_srcu(struct srcu_struct *ssp, struct rcu_head *rhp,
if (WARN_ON_ONCE(ssp->srcu_reader_flavor == SRCU_READ_FLAVOR_ATOMIC))
return; // Leak the callback rather than corrupt SRCU state.
+ if (debug_rcu_head_queue(rhp)) {
+ if (__ratelimit(&rcu_srcu_dup_rs)) {
+ pr_err("%s(): Duplicate call_srcu() CB %p->%pS()\n",
+ __func__, rhp, rhp->func);
+ mem_dump_obj(rhp);
+ dump_stack();
+ }
+ WRITE_ONCE(rhp->func, srcu_leak_callback);
+ return;
+ }
+
/* Record the caller: the irq_work's stack says nothing about it. */
kasan_record_aux_stack(rhp);
@@ -1554,6 +1565,7 @@ static void __call_srcu(struct srcu_struct *ssp, struct rcu_head *rhp,
if (READ_ONCE(sndp->draining) && !in_nmi()) {
WARN_ONCE(IS_ENABLED(CONFIG_PROVE_RCU),
"call_srcu() re-entered during callback drain; leaking callback\n");
+ debug_rcu_head_unqueue(rhp);
return;
}
sdp = this_cpu_ptr(ssp->sda);
--
2.43.0