* [PATCH 6.18.y] rcu-tasks: Defer IRQ-disabled callback enqueue to irq_work
@ 2026-07-10 9:53 Matt Fleming
2026-07-10 18:16 ` Paul E. McKenney
0 siblings, 1 reply; 4+ messages in thread
From: Matt Fleming @ 2026-07-10 9:53 UTC (permalink / raw)
To: Paul E . McKenney
Cc: Frederic Weisbecker, Neeraj Upadhyay, Joel Fernandes,
Josh Triplett, Boqun Feng, Uladzislau Rezki, Steven Rostedt,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Tejun Heo, Andrea Righi,
rcu, linux-kernel, sched-ext, stable, kernel-team, Matt Fleming
From: Matt Fleming <mfleming@cloudflare.com>
call_rcu_tasks_generic() can be invoked with IRQs disabled (e.g. from
scheduler paths holding rq->lock). On heavy contention the slow path
falls through to acquire rtp->cbs_gbl_lock to switch the flavor to
per-CPU enqueue. Taking cbs_gbl_lock underneath rq->lock inverts the
ordering used elsewhere and can deadlock against tasks-RCU wakeups that
run with cbs_gbl_lock held and then touch the scheduler.
CPU 0 CPU 1
----- -----
sched_ext_free()
task_rq_lock()
raw_spin_lock(rq->lock)
rcu_tasks_one_gp()
raw_spin_lock(cbs_gbl_lock)
scx_exit_task()
bpf_task_storage_delete()
call_rcu_tasks_generic()
raw_spin_lock(cbs_gbl_lock)
<blocks>
_printk()
console_unlock()
try_to_wake_up()
raw_spin_lock(rq->lock)
<blocks>
Split enqueue into a helper and route IRQ-disabled callers through a
lockless per-CPU llist drained by a hard irq_work. The fast path with
IRQs enabled is unchanged; only IRQ-disabled callers are deferred, so we
never acquire rtpcp->cbs_pcpu_lock or cbs_gbl_lock under an unknown
outer lock. rcu_tasks_adjust_cbs() is factored out so both the direct
and deferred paths reach the same expansion logic.
Fixes: ab97152f88a4 ("rcu-tasks: Use more callback queues if contention encountered")
Suggested-by: Paul E. McKenney <paulmck@kernel.org>
Link: https://lore.kernel.org/r/20260609104733.1184001-1-mfleming@cloudflare.com
Cc: stable@vger.kernel.org
Signed-off-by: Matt Fleming <mfleming@cloudflare.com>
---
kernel/rcu/tasks.h | 184 ++++++++++++++++++++++++++++++++++++---------
1 file changed, 148 insertions(+), 36 deletions(-)
diff --git a/kernel/rcu/tasks.h b/kernel/rcu/tasks.h
index 2dc044fd126e..56b51946bc6b 100644
--- a/kernel/rcu/tasks.h
+++ b/kernel/rcu/tasks.h
@@ -30,6 +30,8 @@ typedef void (*postgp_func_t)(struct rcu_tasks *rtp);
* @rtp_n_lock_retries: Rough lock-contention statistic.
* @rtp_work: Work queue for invoking callbacks.
* @rtp_irq_work: IRQ work queue for deferred wakeups.
+ * @rtp_irq_bypass_work: IRQ work queue for draining IRQ-bypassed callbacks.
+ * @rtp_irq_bypass_list: Lockless callback list for IRQ-disabled callers.
* @barrier_q_head: RCU callback for barrier operation.
* @rtp_blkd_tasks: List of tasks blocked as readers.
* @rtp_exit_list: List of tasks in the latter portion of do_exit().
@@ -46,6 +48,8 @@ struct rcu_tasks_percpu {
unsigned int urgent_gp;
struct work_struct rtp_work;
struct irq_work rtp_irq_work;
+ struct irq_work rtp_irq_bypass_work;
+ struct llist_head rtp_irq_bypass_list;
struct rcu_head barrier_q_head;
struct list_head rtp_blkd_tasks;
struct list_head rtp_exit_list;
@@ -129,11 +133,14 @@ struct rcu_tasks {
};
static void call_rcu_tasks_iw_wakeup(struct irq_work *iwp);
+static void call_rcu_tasks_iw_drain_irq_bypass(struct irq_work *iwp);
+static void rcu_tasks_adjust_cbs(struct rcu_tasks *rtp);
#define DEFINE_RCU_TASKS(rt_name, gp, call, n) \
static DEFINE_PER_CPU(struct rcu_tasks_percpu, rt_name ## __percpu) = { \
.lock = __RAW_SPIN_LOCK_UNLOCKED(rt_name ## __percpu.cbs_pcpu_lock), \
.rtp_irq_work = IRQ_WORK_INIT_HARD(call_rcu_tasks_iw_wakeup), \
+ .rtp_irq_bypass_work = IRQ_WORK_INIT_HARD(call_rcu_tasks_iw_drain_irq_bypass), \
}; \
static struct rcu_tasks rt_name = \
{ \
@@ -276,6 +283,7 @@ static void cblist_init_generic(struct rcu_tasks *rtp)
if (rcu_segcblist_empty(&rtpcp->cblist))
rcu_segcblist_init(&rtpcp->cblist);
INIT_WORK(&rtpcp->rtp_work, rcu_tasks_invoke_cbs_wq);
+ init_llist_head(&rtpcp->rtp_irq_bypass_list);
rtpcp->cpu = cpu;
rtpcp->rtpp = rtp;
rtpcp->index = index;
@@ -332,6 +340,102 @@ static void call_rcu_tasks_generic_timer(struct timer_list *tlp)
rcuwait_wake_up(&rtp->cbs_wait);
}
+static bool call_rcu_tasks_enqueue_locked(struct rcu_head *rhp,
+ struct rcu_tasks *rtp,
+ struct rcu_tasks_percpu *rtpcp)
+{
+ bool havekthread;
+ bool needwake;
+
+ // Queuing callbacks before initialization not yet supported.
+ if (WARN_ON_ONCE(!rcu_segcblist_is_enabled(&rtpcp->cblist)))
+ rcu_segcblist_init(&rtpcp->cblist);
+ /* Pairs with the kthread publication in rcu_tasks_kthread(). */
+ havekthread = smp_load_acquire(&rtp->kthread_ptr);
+ needwake = (rhp->func == wakeme_after_rcu) ||
+ (rcu_segcblist_n_cbs(&rtpcp->cblist) == rcu_task_lazy_lim);
+ if (havekthread && !needwake && !timer_pending(&rtpcp->lazy_timer)) {
+ if (rtp->lazy_jiffies)
+ mod_timer(&rtpcp->lazy_timer, rcu_tasks_lazy_time(rtp));
+ else
+ needwake = rcu_segcblist_empty(&rtpcp->cblist);
+ }
+ if (needwake)
+ rtpcp->urgent_gp = 3;
+ rcu_segcblist_enqueue(&rtpcp->cblist, rhp);
+ return needwake;
+}
+
+/* Acquire the callback-list lock and report whether the acquisition contended. */
+static bool call_rcu_tasks_lock(struct rcu_tasks_percpu *rtpcp)
+{
+ if (raw_spin_trylock_rcu_node(rtpcp)) // irqs already disabled.
+ return false;
+ raw_spin_lock_rcu_node(rtpcp); // irqs already disabled.
+ return true;
+}
+
+/* Record one callback-queuing-time contention event. */
+static bool rcu_tasks_need_queue_adjust(struct rcu_tasks *rtp,
+ struct rcu_tasks_percpu *rtpcp)
+{
+ unsigned long j = jiffies;
+
+ if (rtpcp->rtp_jiffies != j) {
+ rtpcp->rtp_jiffies = j;
+ rtpcp->rtp_n_lock_retries = 0;
+ }
+
+ return rcu_task_cb_adjust &&
+ ++rtpcp->rtp_n_lock_retries > rcu_task_contend_lim &&
+ READ_ONCE(rtp->percpu_enqueue_lim) != rcu_task_cpu_ids;
+}
+
+static void rcu_tasks_drain_irq_bypass(struct rcu_tasks_percpu *rtpcp)
+{
+ bool contended;
+ bool needadjust = false;
+ bool needwake = false;
+ unsigned long flags;
+ struct llist_node *llnode;
+ struct llist_node *next;
+ struct rcu_head *rhp;
+ struct rcu_tasks *rtp = rtpcp->rtpp;
+
+ local_irq_save(flags);
+ rcu_read_lock();
+ contended = call_rcu_tasks_lock(rtpcp);
+
+ /*
+ * Serialize the entire bypass-to-cblist transfer with barrier
+ * entrainment so that a barrier cannot pass callbacks in transit.
+ */
+ llnode = llist_del_all(&rtpcp->rtp_irq_bypass_list);
+ if (!llnode) {
+ raw_spin_unlock_irqrestore_rcu_node(rtpcp, flags);
+ rcu_read_unlock();
+ return;
+ }
+
+ if (contended)
+ needadjust = rcu_tasks_need_queue_adjust(rtp, rtpcp);
+
+ llnode = llist_reverse_order(llnode);
+ llist_for_each_safe(llnode, next, llnode) {
+ rhp = (struct rcu_head *)llnode;
+ needwake |= call_rcu_tasks_enqueue_locked(rhp, rtp, rtpcp);
+ }
+ raw_spin_unlock_irqrestore_rcu_node(rtpcp, flags);
+
+ if (unlikely(needadjust))
+ rcu_tasks_adjust_cbs(rtp);
+ rcu_read_unlock();
+
+ /* We can't create the thread unless interrupts are enabled. */
+ if (needwake && READ_ONCE(rtp->kthread_ptr))
+ rcuwait_wake_up(&rtp->cbs_wait);
+}
+
// IRQ-work handler that does deferred wakeup for call_rcu_tasks_generic().
static void call_rcu_tasks_iw_wakeup(struct irq_work *iwp)
{
@@ -342,15 +446,40 @@ static void call_rcu_tasks_iw_wakeup(struct irq_work *iwp)
rcuwait_wake_up(&rtp->cbs_wait);
}
+// IRQ-work handler that drains IRQ-bypassed callbacks.
+static void call_rcu_tasks_iw_drain_irq_bypass(struct irq_work *iwp)
+{
+ struct rcu_tasks_percpu *rtpcp;
+
+ rtpcp = container_of(iwp, struct rcu_tasks_percpu, rtp_irq_bypass_work);
+ rcu_tasks_drain_irq_bypass(rtpcp);
+}
+
+static void rcu_tasks_adjust_cbs(struct rcu_tasks *rtp)
+{
+ unsigned long flags;
+ bool expanded = false;
+
+ raw_spin_lock_irqsave(&rtp->cbs_gbl_lock, flags);
+ if (rtp->percpu_enqueue_lim != rcu_task_cpu_ids) {
+ WRITE_ONCE(rtp->percpu_enqueue_shift, 0);
+ WRITE_ONCE(rtp->percpu_dequeue_lim, rcu_task_cpu_ids);
+ smp_store_release(&rtp->percpu_enqueue_lim, rcu_task_cpu_ids);
+ expanded = true;
+ }
+ raw_spin_unlock_irqrestore(&rtp->cbs_gbl_lock, flags);
+ if (expanded)
+ pr_info("Switching %s to per-CPU callback queuing.\n", rtp->name);
+}
+
// Enqueue a callback for the specified flavor of Tasks RCU.
static void call_rcu_tasks_generic(struct rcu_head *rhp, rcu_callback_t func,
struct rcu_tasks *rtp)
{
int chosen_cpu;
unsigned long flags;
- bool havekthread = smp_load_acquire(&rtp->kthread_ptr);
int ideal_cpu;
- unsigned long j;
+ bool irqsoff = irqs_disabled();
bool needadjust = false;
bool needwake;
struct rcu_tasks_percpu *rtpcp;
@@ -363,43 +492,23 @@ static void call_rcu_tasks_generic(struct rcu_head *rhp, rcu_callback_t func,
chosen_cpu = cpumask_next(ideal_cpu - 1, cpu_possible_mask);
WARN_ON_ONCE(chosen_cpu >= rcu_task_cpu_ids);
rtpcp = per_cpu_ptr(rtp->rtpcpu, chosen_cpu);
- if (!raw_spin_trylock_rcu_node(rtpcp)) { // irqs already disabled.
- raw_spin_lock_rcu_node(rtpcp); // irqs already disabled.
- j = jiffies;
- if (rtpcp->rtp_jiffies != j) {
- rtpcp->rtp_jiffies = j;
- rtpcp->rtp_n_lock_retries = 0;
- }
- if (rcu_task_cb_adjust && ++rtpcp->rtp_n_lock_retries > rcu_task_contend_lim &&
- READ_ONCE(rtp->percpu_enqueue_lim) != rcu_task_cpu_ids)
- needadjust = true; // Defer adjustment to avoid deadlock.
- }
- // Queuing callbacks before initialization not yet supported.
- if (WARN_ON_ONCE(!rcu_segcblist_is_enabled(&rtpcp->cblist)))
- rcu_segcblist_init(&rtpcp->cblist);
- needwake = (func == wakeme_after_rcu) ||
- (rcu_segcblist_n_cbs(&rtpcp->cblist) == rcu_task_lazy_lim);
- if (havekthread && !needwake && !timer_pending(&rtpcp->lazy_timer)) {
- if (rtp->lazy_jiffies)
- mod_timer(&rtpcp->lazy_timer, rcu_tasks_lazy_time(rtp));
- else
- needwake = rcu_segcblist_empty(&rtpcp->cblist);
+ if (irqsoff) {
+ llist_add((struct llist_node *)rhp, &rtpcp->rtp_irq_bypass_list);
+ rcu_read_unlock();
+ local_irq_restore(flags);
+ irq_work_queue(&rtpcp->rtp_irq_bypass_work);
+ return;
}
- if (needwake)
- rtpcp->urgent_gp = 3;
- rcu_segcblist_enqueue(&rtpcp->cblist, rhp);
+
+ if (call_rcu_tasks_lock(rtpcp))
+ needadjust = rcu_tasks_need_queue_adjust(rtp, rtpcp);
+
+ needwake = call_rcu_tasks_enqueue_locked(rhp, rtp, rtpcp);
raw_spin_unlock_irqrestore_rcu_node(rtpcp, flags);
- if (unlikely(needadjust)) {
- raw_spin_lock_irqsave(&rtp->cbs_gbl_lock, flags);
- if (rtp->percpu_enqueue_lim != rcu_task_cpu_ids) {
- WRITE_ONCE(rtp->percpu_enqueue_shift, 0);
- WRITE_ONCE(rtp->percpu_dequeue_lim, rcu_task_cpu_ids);
- smp_store_release(&rtp->percpu_enqueue_lim, rcu_task_cpu_ids);
- pr_info("Switching %s to per-CPU callback queuing.\n", rtp->name);
- }
- raw_spin_unlock_irqrestore(&rtp->cbs_gbl_lock, flags);
- }
+ if (unlikely(needadjust))
+ rcu_tasks_adjust_cbs(rtp);
rcu_read_unlock();
+
/* We can't create the thread unless interrupts are enabled. */
if (needwake && READ_ONCE(rtp->kthread_ptr))
irq_work_queue(&rtpcp->rtp_irq_work);
@@ -441,6 +550,7 @@ static void __maybe_unused rcu_barrier_tasks_generic(struct rcu_tasks *rtp)
if (cpu >= smp_load_acquire(&rtp->percpu_dequeue_lim))
break;
rtpcp = per_cpu_ptr(rtp->rtpcpu, cpu);
+ rcu_tasks_drain_irq_bypass(rtpcp);
rtpcp->barrier_q_head.func = rcu_barrier_tasks_generic_cb;
raw_spin_lock_irqsave_rcu_node(rtpcp, flags);
if (rcu_segcblist_entrain(&rtpcp->cblist, &rtpcp->barrier_q_head))
@@ -473,6 +583,8 @@ static int rcu_tasks_need_gpcb(struct rcu_tasks *rtp)
continue;
struct rcu_tasks_percpu *rtpcp = per_cpu_ptr(rtp->rtpcpu, cpu);
+ rcu_tasks_drain_irq_bypass(rtpcp);
+
/* Advance and accelerate any new callbacks. */
if (!rcu_segcblist_n_cbs(&rtpcp->cblist))
continue;
--
2.43.0
^ permalink raw reply related [flat|nested] 4+ messages in thread* Re: [PATCH 6.18.y] rcu-tasks: Defer IRQ-disabled callback enqueue to irq_work
2026-07-10 9:53 [PATCH 6.18.y] rcu-tasks: Defer IRQ-disabled callback enqueue to irq_work Matt Fleming
@ 2026-07-10 18:16 ` Paul E. McKenney
2026-07-11 5:56 ` Matt Fleming
0 siblings, 1 reply; 4+ messages in thread
From: Paul E. McKenney @ 2026-07-10 18:16 UTC (permalink / raw)
To: Matt Fleming
Cc: Frederic Weisbecker, Neeraj Upadhyay, Joel Fernandes,
Josh Triplett, Boqun Feng, Uladzislau Rezki, Steven Rostedt,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Tejun Heo, Andrea Righi,
rcu, linux-kernel, sched-ext, stable, kernel-team, Matt Fleming
On Fri, Jul 10, 2026 at 10:53:59AM +0100, Matt Fleming wrote:
> From: Matt Fleming <mfleming@cloudflare.com>
>
> call_rcu_tasks_generic() can be invoked with IRQs disabled (e.g. from
> scheduler paths holding rq->lock). On heavy contention the slow path
> falls through to acquire rtp->cbs_gbl_lock to switch the flavor to
> per-CPU enqueue. Taking cbs_gbl_lock underneath rq->lock inverts the
> ordering used elsewhere and can deadlock against tasks-RCU wakeups that
> run with cbs_gbl_lock held and then touch the scheduler.
>
> CPU 0 CPU 1
> ----- -----
> sched_ext_free()
> task_rq_lock()
> raw_spin_lock(rq->lock)
> rcu_tasks_one_gp()
> raw_spin_lock(cbs_gbl_lock)
> scx_exit_task()
> bpf_task_storage_delete()
> call_rcu_tasks_generic()
> raw_spin_lock(cbs_gbl_lock)
> <blocks>
> _printk()
> console_unlock()
> try_to_wake_up()
> raw_spin_lock(rq->lock)
> <blocks>
>
> Split enqueue into a helper and route IRQ-disabled callers through a
> lockless per-CPU llist drained by a hard irq_work. The fast path with
> IRQs enabled is unchanged; only IRQ-disabled callers are deferred, so we
> never acquire rtpcp->cbs_pcpu_lock or cbs_gbl_lock under an unknown
> outer lock. rcu_tasks_adjust_cbs() is factored out so both the direct
> and deferred paths reach the same expansion logic.
>
> Fixes: ab97152f88a4 ("rcu-tasks: Use more callback queues if contention encountered")
> Suggested-by: Paul E. McKenney <paulmck@kernel.org>
> Link: https://lore.kernel.org/r/20260609104733.1184001-1-mfleming@cloudflare.com
> Cc: stable@vger.kernel.org
> Signed-off-by: Matt Fleming <mfleming@cloudflare.com>
This does look plausible, thank you! However, it does not apply cleanly
to either current mainline or my -rcu tree.
Judging from the subject line, this is against v6.18 rather than current
mainline, correct? If so, would you be willing to forward-port it?
Thanx, Paul
> ---
> kernel/rcu/tasks.h | 184 ++++++++++++++++++++++++++++++++++++---------
> 1 file changed, 148 insertions(+), 36 deletions(-)
>
> diff --git a/kernel/rcu/tasks.h b/kernel/rcu/tasks.h
> index 2dc044fd126e..56b51946bc6b 100644
> --- a/kernel/rcu/tasks.h
> +++ b/kernel/rcu/tasks.h
> @@ -30,6 +30,8 @@ typedef void (*postgp_func_t)(struct rcu_tasks *rtp);
> * @rtp_n_lock_retries: Rough lock-contention statistic.
> * @rtp_work: Work queue for invoking callbacks.
> * @rtp_irq_work: IRQ work queue for deferred wakeups.
> + * @rtp_irq_bypass_work: IRQ work queue for draining IRQ-bypassed callbacks.
> + * @rtp_irq_bypass_list: Lockless callback list for IRQ-disabled callers.
> * @barrier_q_head: RCU callback for barrier operation.
> * @rtp_blkd_tasks: List of tasks blocked as readers.
> * @rtp_exit_list: List of tasks in the latter portion of do_exit().
> @@ -46,6 +48,8 @@ struct rcu_tasks_percpu {
> unsigned int urgent_gp;
> struct work_struct rtp_work;
> struct irq_work rtp_irq_work;
> + struct irq_work rtp_irq_bypass_work;
> + struct llist_head rtp_irq_bypass_list;
> struct rcu_head barrier_q_head;
> struct list_head rtp_blkd_tasks;
> struct list_head rtp_exit_list;
> @@ -129,11 +133,14 @@ struct rcu_tasks {
> };
>
> static void call_rcu_tasks_iw_wakeup(struct irq_work *iwp);
> +static void call_rcu_tasks_iw_drain_irq_bypass(struct irq_work *iwp);
> +static void rcu_tasks_adjust_cbs(struct rcu_tasks *rtp);
>
> #define DEFINE_RCU_TASKS(rt_name, gp, call, n) \
> static DEFINE_PER_CPU(struct rcu_tasks_percpu, rt_name ## __percpu) = { \
> .lock = __RAW_SPIN_LOCK_UNLOCKED(rt_name ## __percpu.cbs_pcpu_lock), \
> .rtp_irq_work = IRQ_WORK_INIT_HARD(call_rcu_tasks_iw_wakeup), \
> + .rtp_irq_bypass_work = IRQ_WORK_INIT_HARD(call_rcu_tasks_iw_drain_irq_bypass), \
> }; \
> static struct rcu_tasks rt_name = \
> { \
> @@ -276,6 +283,7 @@ static void cblist_init_generic(struct rcu_tasks *rtp)
> if (rcu_segcblist_empty(&rtpcp->cblist))
> rcu_segcblist_init(&rtpcp->cblist);
> INIT_WORK(&rtpcp->rtp_work, rcu_tasks_invoke_cbs_wq);
> + init_llist_head(&rtpcp->rtp_irq_bypass_list);
> rtpcp->cpu = cpu;
> rtpcp->rtpp = rtp;
> rtpcp->index = index;
> @@ -332,6 +340,102 @@ static void call_rcu_tasks_generic_timer(struct timer_list *tlp)
> rcuwait_wake_up(&rtp->cbs_wait);
> }
>
> +static bool call_rcu_tasks_enqueue_locked(struct rcu_head *rhp,
> + struct rcu_tasks *rtp,
> + struct rcu_tasks_percpu *rtpcp)
> +{
> + bool havekthread;
> + bool needwake;
> +
> + // Queuing callbacks before initialization not yet supported.
> + if (WARN_ON_ONCE(!rcu_segcblist_is_enabled(&rtpcp->cblist)))
> + rcu_segcblist_init(&rtpcp->cblist);
> + /* Pairs with the kthread publication in rcu_tasks_kthread(). */
> + havekthread = smp_load_acquire(&rtp->kthread_ptr);
> + needwake = (rhp->func == wakeme_after_rcu) ||
> + (rcu_segcblist_n_cbs(&rtpcp->cblist) == rcu_task_lazy_lim);
> + if (havekthread && !needwake && !timer_pending(&rtpcp->lazy_timer)) {
> + if (rtp->lazy_jiffies)
> + mod_timer(&rtpcp->lazy_timer, rcu_tasks_lazy_time(rtp));
> + else
> + needwake = rcu_segcblist_empty(&rtpcp->cblist);
> + }
> + if (needwake)
> + rtpcp->urgent_gp = 3;
> + rcu_segcblist_enqueue(&rtpcp->cblist, rhp);
> + return needwake;
> +}
> +
> +/* Acquire the callback-list lock and report whether the acquisition contended. */
> +static bool call_rcu_tasks_lock(struct rcu_tasks_percpu *rtpcp)
> +{
> + if (raw_spin_trylock_rcu_node(rtpcp)) // irqs already disabled.
> + return false;
> + raw_spin_lock_rcu_node(rtpcp); // irqs already disabled.
> + return true;
> +}
> +
> +/* Record one callback-queuing-time contention event. */
> +static bool rcu_tasks_need_queue_adjust(struct rcu_tasks *rtp,
> + struct rcu_tasks_percpu *rtpcp)
> +{
> + unsigned long j = jiffies;
> +
> + if (rtpcp->rtp_jiffies != j) {
> + rtpcp->rtp_jiffies = j;
> + rtpcp->rtp_n_lock_retries = 0;
> + }
> +
> + return rcu_task_cb_adjust &&
> + ++rtpcp->rtp_n_lock_retries > rcu_task_contend_lim &&
> + READ_ONCE(rtp->percpu_enqueue_lim) != rcu_task_cpu_ids;
> +}
> +
> +static void rcu_tasks_drain_irq_bypass(struct rcu_tasks_percpu *rtpcp)
> +{
> + bool contended;
> + bool needadjust = false;
> + bool needwake = false;
> + unsigned long flags;
> + struct llist_node *llnode;
> + struct llist_node *next;
> + struct rcu_head *rhp;
> + struct rcu_tasks *rtp = rtpcp->rtpp;
> +
> + local_irq_save(flags);
> + rcu_read_lock();
> + contended = call_rcu_tasks_lock(rtpcp);
> +
> + /*
> + * Serialize the entire bypass-to-cblist transfer with barrier
> + * entrainment so that a barrier cannot pass callbacks in transit.
> + */
> + llnode = llist_del_all(&rtpcp->rtp_irq_bypass_list);
> + if (!llnode) {
> + raw_spin_unlock_irqrestore_rcu_node(rtpcp, flags);
> + rcu_read_unlock();
> + return;
> + }
> +
> + if (contended)
> + needadjust = rcu_tasks_need_queue_adjust(rtp, rtpcp);
> +
> + llnode = llist_reverse_order(llnode);
> + llist_for_each_safe(llnode, next, llnode) {
> + rhp = (struct rcu_head *)llnode;
> + needwake |= call_rcu_tasks_enqueue_locked(rhp, rtp, rtpcp);
> + }
> + raw_spin_unlock_irqrestore_rcu_node(rtpcp, flags);
> +
> + if (unlikely(needadjust))
> + rcu_tasks_adjust_cbs(rtp);
> + rcu_read_unlock();
> +
> + /* We can't create the thread unless interrupts are enabled. */
> + if (needwake && READ_ONCE(rtp->kthread_ptr))
> + rcuwait_wake_up(&rtp->cbs_wait);
> +}
> +
> // IRQ-work handler that does deferred wakeup for call_rcu_tasks_generic().
> static void call_rcu_tasks_iw_wakeup(struct irq_work *iwp)
> {
> @@ -342,15 +446,40 @@ static void call_rcu_tasks_iw_wakeup(struct irq_work *iwp)
> rcuwait_wake_up(&rtp->cbs_wait);
> }
>
> +// IRQ-work handler that drains IRQ-bypassed callbacks.
> +static void call_rcu_tasks_iw_drain_irq_bypass(struct irq_work *iwp)
> +{
> + struct rcu_tasks_percpu *rtpcp;
> +
> + rtpcp = container_of(iwp, struct rcu_tasks_percpu, rtp_irq_bypass_work);
> + rcu_tasks_drain_irq_bypass(rtpcp);
> +}
> +
> +static void rcu_tasks_adjust_cbs(struct rcu_tasks *rtp)
> +{
> + unsigned long flags;
> + bool expanded = false;
> +
> + raw_spin_lock_irqsave(&rtp->cbs_gbl_lock, flags);
> + if (rtp->percpu_enqueue_lim != rcu_task_cpu_ids) {
> + WRITE_ONCE(rtp->percpu_enqueue_shift, 0);
> + WRITE_ONCE(rtp->percpu_dequeue_lim, rcu_task_cpu_ids);
> + smp_store_release(&rtp->percpu_enqueue_lim, rcu_task_cpu_ids);
> + expanded = true;
> + }
> + raw_spin_unlock_irqrestore(&rtp->cbs_gbl_lock, flags);
> + if (expanded)
> + pr_info("Switching %s to per-CPU callback queuing.\n", rtp->name);
> +}
> +
> // Enqueue a callback for the specified flavor of Tasks RCU.
> static void call_rcu_tasks_generic(struct rcu_head *rhp, rcu_callback_t func,
> struct rcu_tasks *rtp)
> {
> int chosen_cpu;
> unsigned long flags;
> - bool havekthread = smp_load_acquire(&rtp->kthread_ptr);
> int ideal_cpu;
> - unsigned long j;
> + bool irqsoff = irqs_disabled();
> bool needadjust = false;
> bool needwake;
> struct rcu_tasks_percpu *rtpcp;
> @@ -363,43 +492,23 @@ static void call_rcu_tasks_generic(struct rcu_head *rhp, rcu_callback_t func,
> chosen_cpu = cpumask_next(ideal_cpu - 1, cpu_possible_mask);
> WARN_ON_ONCE(chosen_cpu >= rcu_task_cpu_ids);
> rtpcp = per_cpu_ptr(rtp->rtpcpu, chosen_cpu);
> - if (!raw_spin_trylock_rcu_node(rtpcp)) { // irqs already disabled.
> - raw_spin_lock_rcu_node(rtpcp); // irqs already disabled.
> - j = jiffies;
> - if (rtpcp->rtp_jiffies != j) {
> - rtpcp->rtp_jiffies = j;
> - rtpcp->rtp_n_lock_retries = 0;
> - }
> - if (rcu_task_cb_adjust && ++rtpcp->rtp_n_lock_retries > rcu_task_contend_lim &&
> - READ_ONCE(rtp->percpu_enqueue_lim) != rcu_task_cpu_ids)
> - needadjust = true; // Defer adjustment to avoid deadlock.
> - }
> - // Queuing callbacks before initialization not yet supported.
> - if (WARN_ON_ONCE(!rcu_segcblist_is_enabled(&rtpcp->cblist)))
> - rcu_segcblist_init(&rtpcp->cblist);
> - needwake = (func == wakeme_after_rcu) ||
> - (rcu_segcblist_n_cbs(&rtpcp->cblist) == rcu_task_lazy_lim);
> - if (havekthread && !needwake && !timer_pending(&rtpcp->lazy_timer)) {
> - if (rtp->lazy_jiffies)
> - mod_timer(&rtpcp->lazy_timer, rcu_tasks_lazy_time(rtp));
> - else
> - needwake = rcu_segcblist_empty(&rtpcp->cblist);
> + if (irqsoff) {
> + llist_add((struct llist_node *)rhp, &rtpcp->rtp_irq_bypass_list);
> + rcu_read_unlock();
> + local_irq_restore(flags);
> + irq_work_queue(&rtpcp->rtp_irq_bypass_work);
> + return;
> }
> - if (needwake)
> - rtpcp->urgent_gp = 3;
> - rcu_segcblist_enqueue(&rtpcp->cblist, rhp);
> +
> + if (call_rcu_tasks_lock(rtpcp))
> + needadjust = rcu_tasks_need_queue_adjust(rtp, rtpcp);
> +
> + needwake = call_rcu_tasks_enqueue_locked(rhp, rtp, rtpcp);
> raw_spin_unlock_irqrestore_rcu_node(rtpcp, flags);
> - if (unlikely(needadjust)) {
> - raw_spin_lock_irqsave(&rtp->cbs_gbl_lock, flags);
> - if (rtp->percpu_enqueue_lim != rcu_task_cpu_ids) {
> - WRITE_ONCE(rtp->percpu_enqueue_shift, 0);
> - WRITE_ONCE(rtp->percpu_dequeue_lim, rcu_task_cpu_ids);
> - smp_store_release(&rtp->percpu_enqueue_lim, rcu_task_cpu_ids);
> - pr_info("Switching %s to per-CPU callback queuing.\n", rtp->name);
> - }
> - raw_spin_unlock_irqrestore(&rtp->cbs_gbl_lock, flags);
> - }
> + if (unlikely(needadjust))
> + rcu_tasks_adjust_cbs(rtp);
> rcu_read_unlock();
> +
> /* We can't create the thread unless interrupts are enabled. */
> if (needwake && READ_ONCE(rtp->kthread_ptr))
> irq_work_queue(&rtpcp->rtp_irq_work);
> @@ -441,6 +550,7 @@ static void __maybe_unused rcu_barrier_tasks_generic(struct rcu_tasks *rtp)
> if (cpu >= smp_load_acquire(&rtp->percpu_dequeue_lim))
> break;
> rtpcp = per_cpu_ptr(rtp->rtpcpu, cpu);
> + rcu_tasks_drain_irq_bypass(rtpcp);
> rtpcp->barrier_q_head.func = rcu_barrier_tasks_generic_cb;
> raw_spin_lock_irqsave_rcu_node(rtpcp, flags);
> if (rcu_segcblist_entrain(&rtpcp->cblist, &rtpcp->barrier_q_head))
> @@ -473,6 +583,8 @@ static int rcu_tasks_need_gpcb(struct rcu_tasks *rtp)
> continue;
> struct rcu_tasks_percpu *rtpcp = per_cpu_ptr(rtp->rtpcpu, cpu);
>
> + rcu_tasks_drain_irq_bypass(rtpcp);
> +
> /* Advance and accelerate any new callbacks. */
> if (!rcu_segcblist_n_cbs(&rtpcp->cblist))
> continue;
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 4+ messages in thread* Re: [PATCH 6.18.y] rcu-tasks: Defer IRQ-disabled callback enqueue to irq_work
2026-07-10 18:16 ` Paul E. McKenney
@ 2026-07-11 5:56 ` Matt Fleming
2026-07-11 23:57 ` Paul E. McKenney
0 siblings, 1 reply; 4+ messages in thread
From: Matt Fleming @ 2026-07-11 5:56 UTC (permalink / raw)
To: Paul E. McKenney
Cc: Frederic Weisbecker, Neeraj Upadhyay, Joel Fernandes,
Josh Triplett, Boqun Feng, Uladzislau Rezki, Steven Rostedt,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Tejun Heo, Andrea Righi,
rcu, linux-kernel, sched-ext, stable, kernel-team, Matt Fleming
On Fri, Jul 10, 2026 at 11:16:27AM -0700, Paul E. McKenney wrote:
>
> This does look plausible, thank you! However, it does not apply cleanly
> to either current mainline or my -rcu tree.
>
> Judging from the subject line, this is against v6.18 rather than current
> mainline, correct? If so, would you be willing to forward-port it?
Yeah, my bad. Since the SRCU code in mainline fixed the original
deadlock I reported I kept this to 6.18. But I'm happy to forward port
this patch too.
Thanks,
Matt
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH 6.18.y] rcu-tasks: Defer IRQ-disabled callback enqueue to irq_work
2026-07-11 5:56 ` Matt Fleming
@ 2026-07-11 23:57 ` Paul E. McKenney
0 siblings, 0 replies; 4+ messages in thread
From: Paul E. McKenney @ 2026-07-11 23:57 UTC (permalink / raw)
To: Matt Fleming
Cc: Frederic Weisbecker, Neeraj Upadhyay, Joel Fernandes,
Josh Triplett, Boqun Feng, Uladzislau Rezki, Steven Rostedt,
Mathieu Desnoyers, Lai Jiangshan, Zqiang, Tejun Heo, Andrea Righi,
rcu, linux-kernel, sched-ext, stable, kernel-team, Matt Fleming
On Sat, Jul 11, 2026 at 06:56:18AM +0100, Matt Fleming wrote:
> On Fri, Jul 10, 2026 at 11:16:27AM -0700, Paul E. McKenney wrote:
> >
> > This does look plausible, thank you! However, it does not apply cleanly
> > to either current mainline or my -rcu tree.
> >
> > Judging from the subject line, this is against v6.18 rather than current
> > mainline, correct? If so, would you be willing to forward-port it?
>
> Yeah, my bad. Since the SRCU code in mainline fixed the original
> deadlock I reported I kept this to 6.18. But I'm happy to forward port
> this patch too.
It would be good in order to avoid similar issues with call_rcu_tasks()
and call_rcu_tasks_rude(), so please do!
Thanx, Paul
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-07-11 23:57 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-10 9:53 [PATCH 6.18.y] rcu-tasks: Defer IRQ-disabled callback enqueue to irq_work Matt Fleming
2026-07-10 18:16 ` Paul E. McKenney
2026-07-11 5:56 ` Matt Fleming
2026-07-11 23:57 ` Paul E. McKenney
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox