2023-05-10 17:30:12

by Paul E. McKenney

[permalink] [raw]
Subject: [PATCH rcu 0/4] Callback-offloading updates for v6.5

Hello!

This series provides some nocb callback-offloading updates:

1. rcu/nocb: Protect lazy shrinker against concurrent
(de-)offloading, courtesy of Frederic Weisbecker.

2. rcu/nocb: Fix shrinker race against callback enqueuer, courtesy
of Frederic Weisbecker.

3. rcu/nocb: Recheck lazy callbacks under the ->nocb_lock from
shrinker, courtesy of Frederic Weisbecker.

4. rcu/nocb: Make shrinker to iterate only NOCB CPUs, courtesy of
Frederic Weisbecker.

Thanx, Paul

------------------------------------------------------------------------

b/kernel/rcu/tree_nocb.h | 25 ++++++++++++++++++++++++-
kernel/rcu/tree_nocb.h | 33 ++++++++++++++++++++++++++-------
2 files changed, 50 insertions(+), 8 deletions(-)


2023-05-10 17:31:20

by Paul E. McKenney

[permalink] [raw]
Subject: [PATCH rcu 4/4] rcu/nocb: Make shrinker to iterate only NOCB CPUs

From: Frederic Weisbecker <[email protected]>

Callbacks can only be queued as lazy on NOCB CPUs, therefore iterating
over the NOCB mask is enough for both counting and scanning. Just lock
the mostly uncontended barrier mutex on counting as well in order to
keep rcu_nocb_mask stable.

Signed-off-by: Frederic Weisbecker <[email protected]>
Signed-off-by: Paul E. McKenney <[email protected]>
---
kernel/rcu/tree_nocb.h | 17 ++++++++++++++---
1 file changed, 14 insertions(+), 3 deletions(-)

diff --git a/kernel/rcu/tree_nocb.h b/kernel/rcu/tree_nocb.h
index dfa9c10d6727..43229d2b0c44 100644
--- a/kernel/rcu/tree_nocb.h
+++ b/kernel/rcu/tree_nocb.h
@@ -1319,13 +1319,22 @@ lazy_rcu_shrink_count(struct shrinker *shrink, struct shrink_control *sc)
int cpu;
unsigned long count = 0;

+ if (WARN_ON_ONCE(!cpumask_available(rcu_nocb_mask)))
+ return 0;
+
+ /* Protect rcu_nocb_mask against concurrent (de-)offloading. */
+ if (!mutex_trylock(&rcu_state.barrier_mutex))
+ return 0;
+
/* Snapshot count of all CPUs */
- for_each_possible_cpu(cpu) {
+ for_each_cpu(cpu, rcu_nocb_mask) {
struct rcu_data *rdp = per_cpu_ptr(&rcu_data, cpu);

count += READ_ONCE(rdp->lazy_len);
}

+ mutex_unlock(&rcu_state.barrier_mutex);
+
return count ? count : SHRINK_EMPTY;
}

@@ -1336,6 +1345,8 @@ lazy_rcu_shrink_scan(struct shrinker *shrink, struct shrink_control *sc)
unsigned long flags;
unsigned long count = 0;

+ if (WARN_ON_ONCE(!cpumask_available(rcu_nocb_mask)))
+ return 0;
/*
* Protect against concurrent (de-)offloading. Otherwise nocb locking
* may be ignored or imbalanced.
@@ -1351,11 +1362,11 @@ lazy_rcu_shrink_scan(struct shrinker *shrink, struct shrink_control *sc)
}

/* Snapshot count of all CPUs */
- for_each_possible_cpu(cpu) {
+ for_each_cpu(cpu, rcu_nocb_mask) {
struct rcu_data *rdp = per_cpu_ptr(&rcu_data, cpu);
int _count;

- if (!rcu_rdp_is_offloaded(rdp))
+ if (WARN_ON_ONCE(!rcu_rdp_is_offloaded(rdp)))
continue;

if (!READ_ONCE(rdp->lazy_len))
--
2.40.1


2023-05-10 17:32:05

by Paul E. McKenney

[permalink] [raw]
Subject: [PATCH rcu 3/4] rcu/nocb: Recheck lazy callbacks under the ->nocb_lock from shrinker

From: Frederic Weisbecker <[email protected]>

The ->lazy_len is only checked locklessly. Recheck again under the
->nocb_lock to avoid spending more time on flushing/waking if not
necessary. The ->lazy_len can still increment concurrently (from 1 to
infinity) but under the ->nocb_lock we at least know for sure if there
are lazy callbacks at all (->lazy_len > 0).

Signed-off-by: Frederic Weisbecker <[email protected]>
Signed-off-by: Paul E. McKenney <[email protected]>
---
kernel/rcu/tree_nocb.h | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)

diff --git a/kernel/rcu/tree_nocb.h b/kernel/rcu/tree_nocb.h
index c321fce2af8e..dfa9c10d6727 100644
--- a/kernel/rcu/tree_nocb.h
+++ b/kernel/rcu/tree_nocb.h
@@ -1358,12 +1358,20 @@ lazy_rcu_shrink_scan(struct shrinker *shrink, struct shrink_control *sc)
if (!rcu_rdp_is_offloaded(rdp))
continue;

- _count = READ_ONCE(rdp->lazy_len);
-
- if (_count == 0)
+ if (!READ_ONCE(rdp->lazy_len))
continue;

rcu_nocb_lock_irqsave(rdp, flags);
+ /*
+ * Recheck under the nocb lock. Since we are not holding the bypass
+ * lock we may still race with increments from the enqueuer but still
+ * we know for sure if there is at least one lazy callback.
+ */
+ _count = READ_ONCE(rdp->lazy_len);
+ if (!_count) {
+ rcu_nocb_unlock_irqrestore(rdp, flags);
+ continue;
+ }
WARN_ON_ONCE(!rcu_nocb_flush_bypass(rdp, NULL, jiffies, false));
rcu_nocb_unlock_irqrestore(rdp, flags);
wake_nocb_gp(rdp, false);
--
2.40.1