Kernel KVM virtualization development
 help / color / mirror / Atom feed
From: Uladzislau Rezki <urezki@gmail.com>
To: David Woodhouse <dwmw2@infradead.org>
Cc: paulmck@kernel.org, Sean Christopherson <seanjc@google.com>,
	Boqun Feng <boqun@kernel.org>,
	kvm@vger.kernel.org, rcu@vger.kernel.org
Subject: Re: [PATCH v3 3/7] KVM: pfncache: Use RCU for readers instead of a rwlock
Date: Sun, 9 Aug 2026 17:24:14 +0200	[thread overview]
Message-ID: <anibnhTYdwdn51FF@milan> (raw)
In-Reply-To: <d0fdf8aa4faa4e349cdc0e051b6e211cddd566b0.camel@infradead.org>

On Sun, Aug 09, 2026 at 10:59:59AM +0100, David Woodhouse wrote:
> On Sat, 2026-08-08 at 10:58 -0700, Paul E. McKenney wrote:
> > By the way, good point on all the SRCU instances sharing a common
> > set of workqueues.  More ways to deadlock!  But I don't see having
> > per-srcu_struct sets of dedicated kthreads.  ;-)
> 
> Indeed. Although I did briefly go down the rabbit hole of whether a
> *reader* sleeping in an allocation could compose into the same kind of
> cycle.
> 
> Conclusion: only if something on the reclaim path synchronizes the
> *same* srcu_struct that the reader holds — cross-domain it's only
> latency, since the GP state machine polls and requeues rather than
> capturing a worker. Which becomes a design rule for GPC usage:
> never allocate under srcu_read_lock(&kvm->gpc_srcu), because our
> invalidator *is* on the reaper path. But that's OK because allocating
> inside the existing GPC rwlock is already verboten.
> 
> > True, but shouldn't we take as much pressure off of the spare as we can
> > so that it will be there for us when we really need it.
> [...]
> > Why not do a "GFP_NOWAIT | __GFP_NOWARN" attempt before raiding
> > srcu_spare_nodes?  Wouldn't that increase the probability that there
> > would be an srcu_node array available when someone really needed it?
> 
> Makes sense. Done that way below: the GFP_NOWAIT attempt comes first,
> so in the common no-pressure case the spare is never touched and is
> guaranteed present under the memory pressure it exists for. That also
> makes the replenish latency mostly moot — it only matters after an
> allocation has already failed under pressure, and nothing ever waits on
> it.
> 
> > Mightn't !try_cmpxchg() be a better fit here?  You are using the returned
> > pointer as a boolean anyway.  (One could also argue for xchg(), but why
> > unnecessarily write to that poor cache line?)
> 
> Also done, plus a check of srcu_spare_nodes before the kzalloc as you
> suggested — the collision is indeed low-probability, but the check is
> free.
> 
> > And the across-SRCU shared-workqueue deadlock that you pointed out is
> > avoided because the only way that gfp_flags is set to GFP_KERNEL is when
> > the caller is supplying its own task, correct?
> 
> Right. After this patch the only GFP_KERNEL caller of
> init_srcu_struct_nodes() is init_srcu_struct() in the caller's own
> task, where blocking is permitted. srcu_gp_end() passes GFP_NOWAIT, so
> nothing on the grace-period workqueue can ever block in reclaim.
> 
> In the meantime, testing found some issues in my original conversion of
> the GPC code to RCU — dropping gpc->lock broke the atomicity of the
> final invalidation check against the publish, and the teardown paths
> could skip the grace period when an invalidation had already cleared
> the valid flag — re-breaking the syzbot thing I only just fixed, but
> for which thankfully I had a repro case right there ready to catch it
> :)
> 
> Both reworked: the valid/becoming-valid state now lives in a single
> atomic word, so the publish is a cmpxchg which an invalidation can
> veto. (My old needs_invalidation flag back again!). That's now ~30
> hours into a 48-hour KASAN+lockdep soak with no complaints, and syzbot
> is chewing on it too.
> 
> Tree with all of that plus this SRCU preallocation patch on top:
> 
> https://git.infradead.org/?p=users/dwmw2/linux.git;a=shortlog;h=refs/heads/xen-rcu-srcu-prealloc
> 
> Patch below. Still only compile-tested — my metal test hosts are
> over the big_cpu_lim threshold, so the lazy transition path this
> changes never executes there; testing it properly wants a small guest
> or big_cpu_lim= tweaking, which is on the list. But also it's a PITA to
> actually *trigger* the OOM reaper path anyway, and I've not actually
> managed it without hacking the kernel to introduce delays.
> 
> From: David Woodhouse <dwmw@amazon.co.uk>
> Subject: [PATCH] srcu: Keep a spare node array so srcu_gp_end() need not block
>  in reclaim
> 
> The one-time transition of an srcu_struct from SRCU_SIZE_SMALL to
> SRCU_SIZE_BIG allocates the srcu_node combining tree with GFP_KERNEL
> from srcu_gp_end(). That runs on the same workqueue which processes
> grace periods for every srcu_struct in the system — including grace
> periods awaited from OOM/reclaim contexts such as the OOM reaper
> calling synchronize_srcu() via an mmu_notifier. If the allocation
> blocks in direct reclaim, it can be waiting on the very OOM reaper
> whose grace period is queued behind it: a deadlock.
> 
> The allocation is literally one size fits all: it depends only on
> rcu_num_nodes, which is fixed once rcu_init_geometry() has run. So
> keep a single preallocated spare array, primed in srcu_init() when
> lazy (contention-triggered) sizing is in effect.
> 
> Allocation tries GFP_NOWAIT first, which in the common no-pressure
> case succeeds and leaves the spare untouched, so that it is still
> there when there really is pressure. Only when that fails is the
> spare consumed (with xchg(), so double-consumption is impossible),
> and the consumer kicks a replenish worker on system_wq — a clean
> context where GFP_KERNEL is safe and nothing waits on the result.
> The final fallback uses the caller's own flags: GFP_KERNEL only ever
> from init_srcu_struct() in the caller's own task, where blocking is
> permitted; srcu_gp_end() passes GFP_NOWAIT, preserving the guarantee
> that the grace-period workqueue never blocks in reclaim.
> 
> Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
> Assisted-by: Claude:claude-mythos-5
> ---
>  kernel/rcu/srcutree.c | 84 +++++++++++++++++++++++++++++++++++++++++--
>  1 file changed, 81 insertions(+), 3 deletions(-)
> 
> diff --git a/kernel/rcu/srcutree.c b/kernel/rcu/srcutree.c
> index 7c2f7cc131f7..23911fa71c64 100644
> --- a/kernel/rcu/srcutree.c
> +++ b/kernel/rcu/srcutree.c
> @@ -123,6 +123,71 @@ static inline bool srcu_invl_snp_seq(unsigned long s)
>  	return s == SRCU_SNP_INIT_SEQ;
>  }
>  
> +/*
> + * A standing spare srcu_node array. The size of the allocation depends
> + * only on rcu_num_nodes, which is fixed once rcu_init_geometry() has run,
> + * so one preallocated array fits every srcu_struct in the system.
> + *
> + * This exists because srcu_gp_end() may need to allocate the array when
> + * a size transition is triggered by contention, and srcu_gp_end() runs
> + * on the same workqueue for every srcu_struct — including grace periods
> + * awaited from OOM/reclaim contexts (e.g. the OOM reaper via an
> + * mmu_notifier). Blocking there in GFP_KERNEL reclaim can deadlock: the
> + * reclaim may be waiting on the very OOM reaper whose grace period is
> + * queued behind this allocation.
> + *
> + * The allocation therefore tries GFP_NOWAIT first — which in the common
> + * no-pressure case succeeds and leaves the spare untouched — and raids
> + * the spare only when that fails, i.e. under the memory pressure the
> + * spare exists for. The spare is replenished from a clean context on
> + * system_wq. Nothing on the grace-period path ever blocks in reclaim.
> + */
> +static struct srcu_node *srcu_spare_nodes;
> +
> +static void srcu_spare_replenish_wq(struct work_struct *work)
> +{
> +	struct srcu_node *spare, *expect = NULL;
> +
> +	if (READ_ONCE(srcu_spare_nodes))
> +		return;		/* Already refilled. */
> +
> +	spare = kzalloc_objs(*spare, rcu_num_nodes, GFP_KERNEL);
> +	if (!spare)
> +		return;
> +	if (!try_cmpxchg(&srcu_spare_nodes, &expect, spare))
> +		kfree(spare);	/* Someone else refilled it first. */
> +}
> +static DECLARE_WORK(srcu_spare_replenish_work, srcu_spare_replenish_wq);
> +
> +static struct srcu_node *srcu_alloc_nodes(gfp_t gfp_flags)
> +{
> +	struct srcu_node *node;
> +
> +	/*
> +	 * Try a non-blocking allocation first, leaving the spare untouched
> +	 * in the common no-pressure case so that it is still there when
> +	 * there really is pressure.
> +	 */
> +	node = kzalloc_objs(*node, rcu_num_nodes, GFP_NOWAIT | __GFP_NOWARN);
>
GFP_NOWAIT already contains __GFP_NOWARN. It is odd.

> +	if (node)
> +		return node;
> +
> +	node = xchg(&srcu_spare_nodes, NULL);
> +	if (node) {
> +		schedule_work(&srcu_spare_replenish_work);
>
I am not sure but if there is a need in doing progress forward, probably
separate wq with WQ_MEM_RECLAIM | WQ_UNBOUND flags is better. It has an
extra rescue kthread to do the progress if no memory or high mem-pressure.

> +		return node;
> +	}
> +
> +	/*
> +	 * Spare already taken and not yet replenished. Fall back to the
> +	 * caller's own flags: for init_srcu_struct() this is GFP_KERNEL in
> +	 * the caller's own task, where blocking is permitted; from
> +	 * srcu_gp_end() it is GFP_NOWAIT again, preserving the guarantee
> +	 * that the grace-period workqueue never blocks in reclaim.
> +	 */
> +	return kzalloc_objs(*node, rcu_num_nodes, gfp_flags);
> +}
> +
>  /*
>   * Allocated and initialize SRCU combining tree.  Returns @true if
>   * allocation succeeded and @false otherwise.
> @@ -139,8 +204,7 @@ static bool init_srcu_struct_nodes(struct srcu_struct *ssp, gfp_t gfp_flags)
>  
>  	/* Initialize geometry if it has not already been initialized. */
>  	rcu_init_geometry();
> -	ssp->srcu_sup->node = kzalloc_objs(*ssp->srcu_sup->node, rcu_num_nodes,
> -					   gfp_flags);
> +	ssp->srcu_sup->node = srcu_alloc_nodes(gfp_flags);
>  	if (!ssp->srcu_sup->node)
>  		return false;
>  
> @@ -1004,7 +1068,7 @@ static void srcu_gp_end(struct srcu_struct *ssp)
>  	/* Transition to big if needed. */
>  	if (ss_state != SRCU_SIZE_SMALL && ss_state != SRCU_SIZE_BIG) {
>  		if (ss_state == SRCU_SIZE_ALLOC)
> -			init_srcu_struct_nodes(ssp, GFP_KERNEL);
> +			init_srcu_struct_nodes(ssp, GFP_NOWAIT | __GFP_NOWARN);
>
Same here.

--
Uladzislau Rezki

  reply	other threads:[~2026-08-09 15:24 UTC|newest]

Thread overview: 32+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-05 19:55 [PATCH v3 0/7] KVM: x86/xen: Fix Xen/GPC/PREEMPT_RT issues with rwlock_t Woodhouse, David
2026-08-05 19:55 ` [PATCH v3 1/7] KVM: pfncache: use a dedicated invalidation sequence for cache refresh Woodhouse, David
2026-08-05 19:55 ` [PATCH v3 2/7] mm/mmu_notifier: Remove non_block_start/end() from notifier invocation Woodhouse, David
2026-08-05 19:55 ` [PATCH v3 3/7] KVM: pfncache: Use RCU for readers instead of a rwlock Woodhouse, David
2026-08-05 20:36   ` sashiko-bot
2026-08-06 16:53     ` Sean Christopherson
2026-08-06 17:58       ` Woodhouse, David
2026-08-06 18:11         ` Sean Christopherson
2026-08-06 18:23           ` Woodhouse, David
2026-08-07  8:56           ` Woodhouse, David
2026-08-07 10:48             ` David Woodhouse
2026-08-06 20:38         ` David Woodhouse
2026-08-06 21:52           ` Paul E. McKenney
2026-08-06 22:02             ` David Woodhouse
2026-08-07 21:55               ` Paul E. McKenney
2026-08-08  7:09                 ` David Woodhouse
2026-08-08 10:09                   ` David Woodhouse
2026-08-08 17:58                     ` Paul E. McKenney
2026-08-09  9:59                       ` David Woodhouse
2026-08-09 15:24                         ` Uladzislau Rezki [this message]
2026-08-09 17:44                           ` David Woodhouse
2026-08-10 10:22                             ` Uladzislau Rezki
2026-08-10 20:00                             ` David Woodhouse
2026-08-05 19:55 ` [PATCH v3 4/7] KVM: x86/xen: Extract delivery of event to vCPU into a separate helper Woodhouse, David
2026-08-05 20:47   ` sashiko-bot
2026-08-05 22:35     ` David Woodhouse
2026-08-06 10:00       ` David Woodhouse
2026-08-06 14:32         ` David Woodhouse
2026-08-05 19:56 ` [PATCH v3 5/7] KVM: x86/xen: Explicitly tag "shared info" page as never being dirty tracked Woodhouse, David
2026-08-05 19:56 ` [PATCH v3 6/7] KVM: x86/xen: Don't dirty track "vCPU info" page Woodhouse, David
2026-08-05 19:56 ` [PATCH v3 7/7] KVM: x86: Use gfn_to_pfn_cache for steal time / preempted status Woodhouse, David
2026-08-05 21:15   ` sashiko-bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=anibnhTYdwdn51FF@milan \
    --to=urezki@gmail.com \
    --cc=boqun@kernel.org \
    --cc=dwmw2@infradead.org \
    --cc=kvm@vger.kernel.org \
    --cc=paulmck@kernel.org \
    --cc=rcu@vger.kernel.org \
    --cc=seanjc@google.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox