On 7/15/2026 5:30 PM, Paul E. McKenney wrote:
> On Thu, Jun 25, 2026 at 08:43:00PM -0400, Joel Fernandes wrote:
>> 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 <[email protected]>
>> Signed-off-by: Joel Fernandes <[email protected]>
> 
> I am a bit concerned about this one.  We are clearing ->defer_qs_pending,
> but there might still be something that will attempt to complete the
> deferred quiescent state.
> 
> I am nevertheless tentatively pulling it in for further review and
> testing.

Right, I believe that is expected and safe. Something else may well still
attempt to complete the deferred QS after this clear, but I checked
every path and there is a check in rcu_preempt_deferred_qs_irqrestore()
which returns early when special.s is clear and no exp QS is owed, and the
irq_work handler / rcu_core paths check rcu_preempt_need_deferred_qs()
first as well. Whichever gets there first does the work; the rest would be 
no-op.

Also the flag functions like a throttle, but not clearing it at the right times
can also avoid doing real work. So we ought to clear it.

thanks,
--
Joel Fernandes

> 
>                                                       Thanx, Paul
> 
>> ---
>>  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 f58ae29acdef..6f5d31e3f1a3 100644
>> --- a/kernel/rcu/tree_plugin.h
>> +++ b/kernel/rcu/tree_plugin.h
>> @@ -692,9 +692,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
>>

-- 
Joel Fernandes


Reply via email to