Re: [PATCH] srcu: Add try_synchronize_srcu() for callers which can prove readers absent

David Woodhouse <[email protected]>
Newsgroups org.kernel.vger.rcu,org.kernel.vger.kvm,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
Argh, a random syzbot fix has polluted my recipient cache; adding Sean for real.

On Wed, 2026-08-12 at 13:32 +0100, David Woodhouse wrote:
> From: David Woodhouse <[email protected]>
> 
> For a dedicated srcu_struct whose read-side critical sections are
> short, atomic, and usually absent — such as one converted from a
> spinning lock — the common case at synchronize time is that there are
> no readers at all. Even synchronize_srcu_expedited() still costs the
> caller an unconditional sleep and two trips through the SRCU workqueue
> to discover that: tens of microseconds of machinery to wait for
> nothing. And synchronize_srcu()'s auto-expedite heuristic explicitly
> declines to expedite within exp_holdoff (25µs) of the previous grace
> period, which is exactly the regime a burst of back-to-back
> invalidations puts such a domain in.
> 
> Provide try_synchronize_srcu(), which proves the no-readers case
> inline and returns true without sleeping, without the workqueue, and
> without advancing the grace-period sequence (so cookies from
> get_state_synchronize_srcu() and queued callbacks are unaffected). If
> the proof fails, it returns false and the caller falls back:
> 
> 	if (!try_synchronize_srcu(ssp))
> 		synchronize_srcu_expedited(ssp);
> 
> For Tree SRCU the proof sums both epochs' unlock counters, executes a
> full barrier, then sums both epochs' lock counters. Equality proves a
> moment within this function at which no reader existed: a reader
> entering between the sums inflates only the lock sum (spurious
> fallback, safe), and a reader whose increment is unobserved has not
> yet returned from srcu_read_lock() — the barrier pairing with
> __srcu_read_lock() guarantees such a reader sees every store the
> caller made beforehand, so it is not a reader the caller is obliged
> to wait for. Summing both epochs means no index flip is required, and
> without a flip the counter-wrap concerns of
> srcu_readers_active_idx_check() do not arise. Readers of the _fast()
> flavors elide the read-side barrier this depends on; any sign of them
> (in the unlock-side rdm mask, which is gathered unconditionally)
> disqualifies the fast path.
> 
> For Tiny SRCU (!SMP) both nesting counts being zero already proves no
> reader exists — a mid-section reader could only be preempted or in an
> interrupt, either of which leaves its count visibly elevated — and
> program order on the sole CPU provides all the required ordering.
> 
> The immediate motivation is a proposed conversion of KVM's
> gfn_to_pfn_cache to use SRCU¹, where the mmu_notifier invalidation
> path must drain readers of a cache (in the manner of a TLB shootdown)
> before the primary MMU zaps the backing page. Those readers are short
> non-sleeping fast paths, some in contexts which cannot sleep (hardirq
> event channel delivery, the scheduler's sched-out hook); measurement
> under a worst-case invalidation flood shows 98.8% of drains complete inline
> in 4-16µs where the expedited grace period took 32-128µs, with the
> wait dominated by workqueue round-trip latency, not by readers.
> 
> ¹ https://lore.kernel.org/all/[email protected]/
> 
> More potential use cases already exist in the tree with the same
> no-readers-common-case profile: kvm->irq_srcu takes half a dozen
> expedited grace periods in the irqfd and routing-update paths (bursts
> of which, at VM boot, fall inside the exp_holdoff window), and mshv's
> pt_irq_srcu is the same shape.
> 
> Signed-off-by: David Woodhouse <[email protected]>
> Assisted-by: Claude:claude-mythos-5
> ---
> Compile-tested for Tiny SRCU (tinyconfig); the Tree version has had
> the KVM gfn_to_pfn_cache conversion soaking on it under an adversarial
> invalidation flood.
> 
>  include/linux/srcu.h  |  1 +
>  kernel/rcu/srcutiny.c | 23 ++++++++++++++
>  kernel/rcu/srcutree.c | 70 +++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 94 insertions(+)
> 
> diff --git a/include/linux/srcu.h b/include/linux/srcu.h
> index a54ce9e808b9..0d7543c7becc 100644
> --- a/include/linux/srcu.h
> +++ b/include/linux/srcu.h
> @@ -91,6 +91,7 @@ void call_srcu(struct srcu_struct *ssp, struct rcu_head *head,
>  		void (*func)(struct rcu_head *head));
>  void cleanup_srcu_struct(struct srcu_struct *ssp);
>  void synchronize_srcu(struct srcu_struct *ssp);
> +bool try_synchronize_srcu(struct srcu_struct *ssp);
>  
>  #define SRCU_GET_STATE_COMPLETED 0x1
>  
> diff --git a/kernel/rcu/srcutiny.c b/kernel/rcu/srcutiny.c
> index a2e2d516e51b..f07159cae241 100644
> --- a/kernel/rcu/srcutiny.c
> +++ b/kernel/rcu/srcutiny.c
> @@ -260,6 +260,29 @@ void synchronize_srcu(struct srcu_struct *ssp)
>  }
>  EXPORT_SYMBOL_GPL(synchronize_srcu);
>  
> +/**
> + * try_synchronize_srcu - inline grace period for a reader-free srcu_struct
> + * @ssp: srcu_struct with which to synchronize.
> + *
> + * If @ssp provably has no readers in either epoch, provide the
> + * synchronize_srcu() guarantee to the caller immediately, without
> + * sleeping. Returns true on success; on failure the caller must fall
> + * back to synchronize_srcu().
> + *
> + * On !SMP a reader can only be mid-critical-section if it was
> + * preempted (or is running in an interrupt which preempted us), in
> + * which case its nesting count is visibly non-zero. Both counts being
> + * zero therefore proves that no reader exists, and any reader which
> + * begins after this function returns will, by program order on this
> + * sole CPU, observe every store the caller made before calling it.
> + */
> +bool try_synchronize_srcu(struct srcu_struct *ssp)
> +{
> +	return !READ_ONCE(ssp->srcu_lock_nesting[0]) &&
> +	       !READ_ONCE(ssp->srcu_lock_nesting[1]);
> +}
> +EXPORT_SYMBOL_GPL(try_synchronize_srcu);
> +
>  /*
>   * get_state_synchronize_srcu - Provide an end-of-grace-period cookie
>   */
> diff --git a/kernel/rcu/srcutree.c b/kernel/rcu/srcutree.c
> index 2601566c254a..76a78336feaa 100644
> --- a/kernel/rcu/srcutree.c
> +++ b/kernel/rcu/srcutree.c
> @@ -1645,6 +1645,76 @@ void synchronize_srcu(struct srcu_struct *ssp)
>  }
>  EXPORT_SYMBOL_GPL(synchronize_srcu);
>  
> +/**
> + * try_synchronize_srcu - inline grace period for a reader-free srcu_struct
> + * @ssp: srcu_struct with which to synchronize.
> + *
> + * If @ssp provably has no readers in either epoch, provide the
> + * synchronize_srcu() guarantee to the caller immediately: without
> + * sleeping, without a trip through the SRCU workqueue, and without
> + * advancing the grace-period sequence. Returns true on success; on
> + * failure the caller must fall back to synchronize_srcu() or
> + * synchronize_srcu_expedited().
> + *
> + * This serves dedicated srcu_struct structures whose read-side critical
> + * sections are short, atomic, and usually absent — where even an
> + * expedited grace period costs two trips through the workqueue and an
> + * unconditional sleep of the caller, three orders of magnitude more
> + * than the check below.
> + *
> + * Only readers of the srcu_read_lock() and srcu_read_lock_nmisafe()
> + * flavors are compatible with this proof; if the _fast() flavors have
> + * ever been used on @ssp, this function always returns false.
> + */
> +bool try_synchronize_srcu(struct srcu_struct *ssp)
> +{
> +	unsigned long unlocks0, unlocks1;
> +	unsigned long rdm0, rdm1;
> +
> +	check_init_srcu_struct(ssp);
> +
> +	/*
> +	 * Order the caller's prior stores before the counter reads below.
> +	 * Pairs (store-buffering pattern) with the smp_mb() in
> +	 * __srcu_read_lock(): any reader whose lock increment is not
> +	 * observed by the sums below is guaranteed to observe, within its
> +	 * critical section, every store the caller made before calling
> +	 * this function.
> +	 */
> +	smp_mb();
> +
> +	unlocks0 = srcu_readers_unlock_idx(ssp, 0, &rdm0);
> +	unlocks1 = srcu_readers_unlock_idx(ssp, 1, &rdm1);
> +
> +	/*
> +	 * Reader flavors which elide the read-side smp_mb() that the
> +	 * pairing above depends on cannot be proven absent this way;
> +	 * they need a real grace period.
> +	 */
> +	if ((rdm0 | rdm1) & SRCU_READ_FLAVOR_SLOWGP)
> +		return false;
> +
> +	/*
> +	 * As in srcu_readers_active_idx_check(): ensure that a lock is
> +	 * always counted if the corresponding unlock is counted, so that
> +	 * a reader racing with these sums can only inflate the lock sum
> +	 * and force the (safe) fallback. Summing both epochs means no
> +	 * index flip is needed: a stable equality proves there was a
> +	 * moment in this function at which no readers existed at all.
> +	 */
> +	smp_mb();
> +
> +	if (!srcu_readers_lock_idx(ssp, 0, false, unlocks0))
> +		return false;
> +	if (!srcu_readers_lock_idx(ssp, 1, false, unlocks1))
> +		return false;
> +
> +	/* Order the caller's subsequent accesses after the proof. */
> +	smp_mb();
> +	return true;
> +}
> +EXPORT_SYMBOL_GPL(try_synchronize_srcu);
> +
>  /**
>   * get_state_synchronize_srcu - Provide an end-of-grace-period cookie
>   * @ssp: srcu_struct to provide cookie for.
smime.p7s (application/pkcs7-signature, 6 KB) - not displayed
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.