Re: [PATCH v3 3/7] KVM: pfncache: Use RCU for readers instead of a rwlock

David Woodhouse <[email protected]>
Newsgroups org.kernel.vger.rcu,org.kernel.vger.kvm
Message-ID <[email protected]>
On Sat, 2026-08-08 at 08:09 +0100, David Woodhouse wrote:
> 
> As I was reading through, I was landing on "combine #4 and #1": use
> GFP_NOWAIT unconditionally, but on failure trigger a workqueue to
> allocate that memory in a clean environment.

In fact, why even do that as a fallback? This allocation is *literally*
one size fits all, isn't it? The size depends on rcu_num_nodes which is
a boot-time constant.

Just allocate it in *advance* from a safe context, and have
init_srcu_struct_nodes() consume the preallocated one. And if a second
init_srcu_struct_nodes() runs before it can be replenished, *then* fall
back to a direct GFP_NOWAIT allocation.

We'd probably want to take a look at the early boot path, but
GFP_KERNEL is effectively GFP_NOWAIT at that point anyway —
gfp_allowed_mask strips __GFP_RECLAIM until well after srcu_init() has
run.

Something like this, perhaps...

From: David Woodhouse <[email protected]>
Date: Sat, 8 Aug 2026 11:01:52 +0100
Subject: [PATCH] srcu: Keep a spare node array so srcu_gp_end() need not allocate


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, and have
init_srcu_struct_nodes() consume it with xchg(). The consumer kicks a
replenish worker on system_wq (a clean context where GFP_KERNEL is
safe); if a second transition races ahead of the replenish, the
GFP_NOWAIT fallback keeps today's benign fail-and-retry behaviour,
now with no possibility of blocking the grace-period workqueue in
reclaim.

The init-time paths (init_srcu_struct_fields() and boot) also flow
through srcu_alloc_nodes() and may consume the spare; that is
harmless, as the replenish worker refills it.

Not-yet-tested-by: David Woodhouse <[email protected]>
Signed-off-by: David Woodhouse <[email protected]>
Assisted-by: Claude:claude-mythos-5
---
 kernel/rcu/srcutree.c | 64 +++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 61 insertions(+), 3 deletions(-)

diff --git a/kernel/rcu/srcutree.c b/kernel/rcu/srcutree.c
index 7c2f7cc131f7..914a0574dce2 100644
--- a/kernel/rcu/srcutree.c
+++ b/kernel/rcu/srcutree.c
@@ -123,6 +123,51 @@ 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. Consuming a preallocated array instead
+ * keeps the grace-period path allocation-free; the spare is replenished
+ * from a clean context on system_wq, and a direct GFP_NOWAIT attempt
+ * remains as the fallback if a second transition wins the race for the
+ * spare before it can be replenished.
+ */
+static struct srcu_node *srcu_spare_nodes;
+
+static void srcu_spare_replenish_wq(struct work_struct *work)
+{
+	struct srcu_node *spare;
+
+	spare = kzalloc_objs(*spare, rcu_num_nodes, GFP_KERNEL);
+	if (!spare)
+		return;
+	if (cmpxchg(&srcu_spare_nodes, NULL, 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;
+
+	node = xchg(&srcu_spare_nodes, NULL);
+	if (node) {
+		schedule_work(&srcu_spare_replenish_work);
+		return node;
+	}
+
+	/* Spare already taken and not yet replenished. */
+	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 +184,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 +1048,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);
 		else
 			smp_store_release(&sup->srcu_size_state, ss_state + 1);
 	}
@@ -2111,6 +2155,20 @@ void __init srcu_init(void)
 		}
 	}
 
+	/*
+	 * Prime the spare node array if lazy (contention-triggered) size
+	 * transitions are possible, so that srcu_gp_end() never needs to
+	 * allocate. Early-boot GFP_KERNEL is implicitly non-blocking
+	 * (gfp_allowed_mask strips __GFP_RECLAIM until much later), and
+	 * failure here is harmless: the GFP_NOWAIT fallback and replenish
+	 * worker remain.
+	 */
+	if (SRCU_SIZING_IS_CONTEND() || SRCU_SIZING_IS_TORTURE()) {
+		rcu_init_geometry();
+		srcu_spare_nodes = kzalloc_objs(*srcu_spare_nodes,
+						rcu_num_nodes, GFP_KERNEL);
+	}
+
 	/*
 	 * Once that is set, call_srcu() can follow the normal path and
 	 * queue delayed work. This must follow RCU workqueues creation
-- 
2.43.0
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.