Re: [PATCH v2 6/11] rcu: Enable RCU callbacks to benefit from expedited grace periods

"Paul E. McKenney" <[email protected]> Fri, 24 Jul 2026 17:23:49 -0700
Newsgroups org.kernel.vger.rcu,org.kernel.vger.linux-kernel,org.kernel.vger.linux-trace-kernel
Message-ID <5a87e5f1-7cbf-45c0-a6e2-88320db628bc@paulmck-laptop>
On Fri, Jul 24, 2026 at 11:31:23AM -0700, Puranjay Mohan wrote:
> Currently, RCU callbacks only track normal grace-period sequence
> numbers.  This means callbacks must wait for normal grace periods to
> complete even when expedited grace periods have already elapsed.
> 
> Use the full struct rcu_gp_seq (which tracks both the normal and
> expedited grace-period sequences) throughout the callback
> infrastructure.
> 
> rcu_segcblist_advance() now checks both normal and expedited GP
> completion via poll_state_synchronize_rcu_full(), and becomes
> parameterless since it reads the grace-period state internally.
> rcu_segcblist_accelerate() stores the full state (both sequences)
> instead of just the normal one.  rcu_accelerate_cbs() and
> rcu_accelerate_cbs_unlocked() use get_state_synchronize_rcu_full() to
> capture both sequences, and the NOCB advance checks use
> poll_state_synchronize_rcu_full() instead of comparing only the normal
> sequence.
> 
> srcu_segcblist_advance() becomes a standalone implementation because it
> compares SRCU sequences directly and cannot use
> poll_state_synchronize_rcu_full(), which reads RCU-specific globals.
> srcu_segcblist_accelerate() sets the ->exp field to
> RCU_GET_STATE_NOT_TRACKED so that poll_state_synchronize_rcu_full()
> compares only ->norm and ignores ->exp.
> 
> Signed-off-by: Puranjay Mohan <[email protected]>
> ---
> Paul, please take this in place of the original 6/11.  The only change
> from v1 is dropping the premature !poll_state_synchronize_rcu_full() gate
> in nocb_gp_wait(): if a grace period completed between rcu_advance_cbs()
> and that poll, the rcuog slept grace-period-deaf in nocb_gp_sleep() with
> advanceable callbacks still queued -- the lost wakeup Frederic diagnosed.
> Verified by widening that window with a delay: the original reproduced the
> writer stall within ~75s, this version stayed clean.

Done, and will test during the weekend, thank you!

There were some conflicts, so could you please double-check?

							Thanx, Paul

>  kernel/rcu/rcu_segcblist.c | 30 +++++++++++++++++++++++-------
>  kernel/rcu/rcu_segcblist.h |  2 +-
>  kernel/rcu/tree.c          |  9 +++------
>  kernel/rcu/tree_nocb.h     | 22 ++++++++++++----------
>  4 files changed, 39 insertions(+), 24 deletions(-)
> 
> diff --git a/kernel/rcu/rcu_segcblist.c b/kernel/rcu/rcu_segcblist.c
> index 4e3dfe42bc097..cf8951d33e767 100644
> --- a/kernel/rcu/rcu_segcblist.c
> +++ b/kernel/rcu/rcu_segcblist.c
> @@ -12,6 +12,7 @@
>  #include <linux/kernel.h>
>  #include <linux/types.h>
>  
> +#include "rcu.h"
>  #include "rcu_segcblist.h"
>  
>  /* Initialize simple callback list. */
> @@ -494,9 +495,9 @@ static void rcu_segcblist_advance_compact(struct rcu_segcblist *rsclp, int i)
>  
>  /*
>   * Advance the callbacks in the specified rcu_segcblist structure based
> - * on the current value passed in for the grace-period counter.
> + * on the current value of the grace-period counter.
>   */
> -void rcu_segcblist_advance(struct rcu_segcblist *rsclp, struct rcu_gp_seq *gsp)
> +void rcu_segcblist_advance(struct rcu_segcblist *rsclp)
>  {
>  	int i;
>  
> @@ -509,7 +510,7 @@ void rcu_segcblist_advance(struct rcu_segcblist *rsclp, struct rcu_gp_seq *gsp)
>  	 * are ready to invoke, and put them into the RCU_DONE_TAIL segment.
>  	 */
>  	for (i = RCU_WAIT_TAIL; i < RCU_NEXT_TAIL; i++) {
> -		if (ULONG_CMP_LT(gsp->norm, rsclp->gp_seq[i].norm))
> +		if (!poll_state_synchronize_rcu_full(&rsclp->gp_seq[i]))
>  			break;
>  		WRITE_ONCE(rsclp->tails[RCU_DONE_TAIL], rsclp->tails[i]);
>  		rcu_segcblist_move_seglen(rsclp, i, RCU_DONE_TAIL);
> @@ -595,7 +596,7 @@ bool rcu_segcblist_accelerate(struct rcu_segcblist *rsclp, struct rcu_gp_seq *gs
>  	 */
>  	for (; i < RCU_NEXT_TAIL; i++) {
>  		WRITE_ONCE(rsclp->tails[i], rsclp->tails[RCU_NEXT_TAIL]);
> -		rsclp->gp_seq[i].norm = gsp->norm;
> +		rsclp->gp_seq[i] = *gsp;
>  	}
>  	return true;
>  }
> @@ -637,14 +638,29 @@ void rcu_segcblist_merge(struct rcu_segcblist *dst_rsclp,
>  
>  void srcu_segcblist_advance(struct rcu_segcblist *rsclp, unsigned long seq)
>  {
> -	struct rcu_gp_seq gs = { .norm = seq };
> +	int i;
> +
> +	WARN_ON_ONCE(!rcu_segcblist_is_enabled(rsclp));
> +	if (rcu_segcblist_restempty(rsclp, RCU_DONE_TAIL))
> +		return;
> +
> +	for (i = RCU_WAIT_TAIL; i < RCU_NEXT_TAIL; i++) {
> +		if (ULONG_CMP_LT(seq, rsclp->gp_seq[i].norm))
> +			break;
> +		WRITE_ONCE(rsclp->tails[RCU_DONE_TAIL], rsclp->tails[i]);
> +		rcu_segcblist_move_seglen(rsclp, i, RCU_DONE_TAIL);
> +	}
> +
> +	/* If no callbacks moved, nothing more need be done. */
> +	if (i == RCU_WAIT_TAIL)
> +		return;
>  
> -	rcu_segcblist_advance(rsclp, &gs);
> +	rcu_segcblist_advance_compact(rsclp, i);
>  }
>  
>  bool srcu_segcblist_accelerate(struct rcu_segcblist *rsclp, unsigned long seq)
>  {
> -	struct rcu_gp_seq gs = { .norm = seq };
> +	struct rcu_gp_seq gs = { .norm = seq, .exp = RCU_GET_STATE_NOT_TRACKED };
>  
>  	return rcu_segcblist_accelerate(rsclp, &gs);
>  }
> diff --git a/kernel/rcu/rcu_segcblist.h b/kernel/rcu/rcu_segcblist.h
> index 16b0cb6b32507..431c4466b8898 100644
> --- a/kernel/rcu/rcu_segcblist.h
> +++ b/kernel/rcu/rcu_segcblist.h
> @@ -139,7 +139,7 @@ void rcu_segcblist_insert_done_cbs(struct rcu_segcblist *rsclp,
>  				   struct rcu_cblist *rclp);
>  void rcu_segcblist_insert_pend_cbs(struct rcu_segcblist *rsclp,
>  				   struct rcu_cblist *rclp);
> -void rcu_segcblist_advance(struct rcu_segcblist *rsclp, struct rcu_gp_seq *gsp);
> +void rcu_segcblist_advance(struct rcu_segcblist *rsclp);
>  bool rcu_segcblist_accelerate(struct rcu_segcblist *rsclp, struct rcu_gp_seq *gsp);
>  void rcu_segcblist_merge(struct rcu_segcblist *dst_rsclp,
>  			 struct rcu_segcblist *src_rsclp);
> diff --git a/kernel/rcu/tree.c b/kernel/rcu/tree.c
> index 640ebe055a246..f63ce75c5b4f5 100644
> --- a/kernel/rcu/tree.c
> +++ b/kernel/rcu/tree.c
> @@ -1159,7 +1159,7 @@ static bool rcu_accelerate_cbs(struct rcu_node *rnp, struct rcu_data *rdp)
>  	 * accelerating callback invocation to an earlier grace-period
>  	 * number.
>  	 */
> -	gs.norm = rcu_seq_snap(&rcu_state.gp_seq);
> +	get_state_synchronize_rcu_full(&gs);
>  	if (rcu_segcblist_accelerate(&rdp->cblist, &gs))
>  		ret = rcu_start_this_gp(rnp, rdp, gs.norm);
>  
> @@ -1188,7 +1188,7 @@ static void rcu_accelerate_cbs_unlocked(struct rcu_node *rnp,
>  	bool needwake;
>  
>  	rcu_lockdep_assert_cblist_protected(rdp);
> -	gs.norm = rcu_seq_snap(&rcu_state.gp_seq);
> +	get_state_synchronize_rcu_full(&gs);
>  	if (!READ_ONCE(rdp->gpwrap) && ULONG_CMP_GE(rdp->gp_seq_needed, gs.norm)) {
>  		/* Old request still live, so mark recent callbacks. */
>  		(void)rcu_segcblist_accelerate(&rdp->cblist, &gs);
> @@ -1213,8 +1213,6 @@ static void rcu_accelerate_cbs_unlocked(struct rcu_node *rnp,
>   */
>  static bool rcu_advance_cbs(struct rcu_node *rnp, struct rcu_data *rdp)
>  {
> -	struct rcu_gp_seq gs;
> -
>  	rcu_lockdep_assert_cblist_protected(rdp);
>  	raw_lockdep_assert_held_rcu_node(rnp);
>  
> @@ -1226,8 +1224,7 @@ static bool rcu_advance_cbs(struct rcu_node *rnp, struct rcu_data *rdp)
>  	 * Find all callbacks whose ->gp_seq numbers indicate that they
>  	 * are ready to invoke, and put them into the RCU_DONE_TAIL sublist.
>  	 */
> -	gs.norm = rnp->gp_seq;
> -	rcu_segcblist_advance(&rdp->cblist, &gs);
> +	rcu_segcblist_advance(&rdp->cblist);
>  
>  	/* Classify any remaining callbacks. */
>  	return rcu_accelerate_cbs(rnp, rdp);
> diff --git a/kernel/rcu/tree_nocb.h b/kernel/rcu/tree_nocb.h
> index eaa1ad5297e29..876dd897f18be 100644
> --- a/kernel/rcu/tree_nocb.h
> +++ b/kernel/rcu/tree_nocb.h
> @@ -502,7 +502,7 @@ static bool rcu_nocb_try_bypass(struct rcu_data *rdp, struct rcu_head *rhp,
>  		}
>  		if (j != rdp->nocb_gp_adv_time &&
>  		    rcu_segcblist_nextgp(&rdp->cblist, &cur_gp_seq) &&
> -		    rcu_seq_done(&rdp->mynode->gp_seq, cur_gp_seq.norm)) {
> +		    poll_state_synchronize_rcu_full(&cur_gp_seq)) {
>  			rcu_advance_cbs_nowake(rdp->mynode, rdp);
>  			rdp->nocb_gp_adv_time = j;
>  		}
> @@ -731,17 +731,17 @@ static noinline_for_stack void nocb_gp_wait(struct rcu_data *my_rdp)
>  		if (!rcu_segcblist_restempty(&rdp->cblist,
>  					     RCU_NEXT_READY_TAIL) ||
>  		    (rcu_segcblist_nextgp(&rdp->cblist, &cur_gp_seq) &&
> -		     rcu_seq_done(&rnp->gp_seq, cur_gp_seq.norm))) {
> +		     poll_state_synchronize_rcu_full(&cur_gp_seq))) {
>  			raw_spin_lock_rcu_node(rnp); /* irqs disabled. */
>  			needwake_gp = rcu_advance_cbs(rnp, rdp);
>  			wasempty = rcu_segcblist_restempty(&rdp->cblist,
>  							   RCU_NEXT_READY_TAIL);
>  			raw_spin_unlock_rcu_node(rnp); /* irqs disabled. */
>  		}
> -		// Need to wait on some grace period?
>  		WARN_ON_ONCE(wasempty &&
>  			     !rcu_segcblist_restempty(&rdp->cblist,
>  						      RCU_NEXT_READY_TAIL));
> +		// Need to wait on some grace period?
>  		if (rcu_segcblist_nextgp(&rdp->cblist, &cur_gp_seq)) {
>  			if (!needwait_gp ||
>  			    ULONG_CMP_LT(cur_gp_seq.norm, wait_gp_seq))
> @@ -919,7 +919,7 @@ static void nocb_cb_wait(struct rcu_data *rdp)
>  	lockdep_assert_irqs_enabled();
>  	rcu_nocb_lock_irqsave(rdp, flags);
>  	if (rcu_segcblist_nextgp(cblist, &cur_gp_seq) &&
> -	    rcu_seq_done(&rnp->gp_seq, cur_gp_seq.norm) &&
> +	    poll_state_synchronize_rcu_full(&cur_gp_seq) &&
>  	    raw_spin_trylock_rcu_node(rnp)) { /* irqs already disabled. */
>  		needwake_gp = rcu_advance_cbs(rdp->mynode, rdp);
>  		raw_spin_unlock_rcu_node(rnp); /* irqs remain disabled. */
> @@ -1548,8 +1548,8 @@ static void show_rcu_nocb_gp_state(struct rcu_data *rdp)
>  static void show_rcu_nocb_state(struct rcu_data *rdp)
>  {
>  	char bufd[22];
> -	char bufw[45];
> -	char bufr[45];
> +	char bufw[64];
> +	char bufr[64];
>  	char bufn[22];
>  	char bufb[22];
>  	struct rcu_data *nocb_next_rdp;
> @@ -1569,10 +1569,12 @@ static void show_rcu_nocb_state(struct rcu_data *rdp)
>  					      nocb_entry_rdp);
>  
>  	sprintf(bufd, "%ld", rsclp->seglen[RCU_DONE_TAIL]);
> -	sprintf(bufw, "%ld(%ld)", rsclp->seglen[RCU_WAIT_TAIL],
> -		rsclp->gp_seq[RCU_WAIT_TAIL].norm);
> -	sprintf(bufr, "%ld(%ld)", rsclp->seglen[RCU_NEXT_READY_TAIL],
> -		rsclp->gp_seq[RCU_NEXT_READY_TAIL].norm);
> +	sprintf(bufw, "%ld(%ld/%ld)", rsclp->seglen[RCU_WAIT_TAIL],
> +		rsclp->gp_seq[RCU_WAIT_TAIL].norm,
> +		rsclp->gp_seq[RCU_WAIT_TAIL].exp);
> +	sprintf(bufr, "%ld(%ld/%ld)", rsclp->seglen[RCU_NEXT_READY_TAIL],
> +		rsclp->gp_seq[RCU_NEXT_READY_TAIL].norm,
> +		rsclp->gp_seq[RCU_NEXT_READY_TAIL].exp);
>  	sprintf(bufn, "%ld", rsclp->seglen[RCU_NEXT_TAIL]);
>  	sprintf(bufb, "%ld", rcu_cblist_n_cbs(&rdp->nocb_bypass));
>  	pr_info("   CB %d^%d->%d %c%c%c%c%c F%ld L%ld C%d %c%s%c%s%c%s%c%s%c%s q%ld %c CPU %d%s\n",
> -- 
> 2.53.0-Meta
>