[PATCH v5 7/8] NFSD: Pace the state shrinker's scan requests

Chuck Lever <[email protected]>
Newsgroups gmane.linux.nfs
Message-ID <[email protected]>
Currently, neither of the scan callback functions records anything
before returning SHRINK_STOP, so the size of each scan request is
discarded. That size is the only real measure NFSD gets of reclaim
pressure. Both count callbacks report their population whether or
not the reaper is already queued to reclaim it, so reclaim asks
again for work that is pending.

Accumulate each courtesy scan request in nfsd_shrink_backlog and
subtract the backlog from what that count callback reports. The
worker retires the backlog once courtesy_client_reaper() has run.
That reaper expires the clients synchronously, so the discount
covers exactly the interval the work is pending.

Delegations need a different bound. This is because deleg_reaper()
only sends CB_RECALL_ANY and does not track how many delegations
were actually returned by the targeted client.

Report the delegations only once NFSD_RECALL_ANY_COOLDOWN_SECS have
passed since the last sweep. deleg_reaper() skips any client it
recalled from within that window, so an earlier scan request cannot
produce another recall.

Signed-off-by: Chuck Lever <[email protected]>
Reviewed-by: Jeff Layton <[email protected]>
---
 fs/nfsd/netns.h     |  6 ++++
 fs/nfsd/nfs4state.c | 92 +++++++++++++++++++++++++++++++++++++++++++++--------
 2 files changed, 85 insertions(+), 13 deletions(-)

diff --git a/fs/nfsd/netns.h b/fs/nfsd/netns.h
index ef01a1cf72ac..23923cc4aa47 100644
--- a/fs/nfsd/netns.h
+++ b/fs/nfsd/netns.h
@@ -245,6 +245,12 @@ struct nfsd_net {
 	struct work_struct	nfsd_courtesy_work;
 	struct work_struct	nfsd_deleg_work;
 
+	/* courtesy scan requests the reaper has not retired yet */
+	atomic_long_t		nfsd_shrink_backlog;
+
+	/* when deleg_reaper() last swept the client list */
+	time64_t		nfsd_last_recall_any;
+
 	/* last time an admin-revoke happened for NFSv4.0 */
 	time64_t		nfs40_last_revoke;
 
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index 2dbc49a6dcaa..1e71bb7a29c8 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -5561,41 +5561,88 @@ nfsd4_init_slabs(void)
 	return -ENOMEM;
 }
 
+#define NFSD_RECALL_ANY_COOLDOWN_SECS	5
+
 static unsigned long
 nfsd4_courtesy_shrinker_count(struct shrinker *shrink,
 			      struct shrink_control *sc)
 {
 	struct nfsd_net *nn = shrink->private_data;
-	long count;
+	long backlog, count;
 
 	count = atomic_read(&nn->nfsd_courtesy_clients);
-	if (count)
-		queue_work(laundry_wq, &nn->nfsd_courtesy_work);
-	return (unsigned long)count;
+	if (!count)
+		return 0;
+
+	queue_work(laundry_wq, &nn->nfsd_courtesy_work);
+
+	/* Work already queued is not available to reclaim again. */
+	backlog = atomic_long_read(&nn->nfsd_shrink_backlog);
+	return count > backlog ? count - backlog : 0;
 }
 
 static unsigned long
 nfsd4_deleg_shrinker_count(struct shrinker *shrink, struct shrink_control *sc)
 {
 	struct nfsd_net *nn = shrink->private_data;
+	time64_t elapsed;
 	long count;
 
 	count = atomic_long_read(&nn->nfsd_delegations);
-	if (count)
-		queue_work(laundry_wq, &nn->nfsd_deleg_work);
-	return (unsigned long)count;
+	if (!count)
+		return 0;
+
+	/*
+	 * Delegations the last sweep reached stay unreclaimable until
+	 * deleg_reaper()'s cooldown expires. CB_RECALL_ANY leaves the
+	 * choice of delegations to the client, so there is no return
+	 * to wait on instead.
+	 */
+	elapsed = ktime_get_boottime_seconds() -
+			READ_ONCE(nn->nfsd_last_recall_any);
+	if (elapsed < NFSD_RECALL_ANY_COOLDOWN_SECS)
+		return 0;
+
+	queue_work(laundry_wq, &nn->nfsd_deleg_work);
+	return count;
 }
 
 static unsigned long
-nfsd4_state_shrinker_scan(struct shrinker *shrink, struct shrink_control *sc)
+nfsd4_courtesy_shrinker_scan(struct shrinker *shrink,
+			     struct shrink_control *sc)
 {
+	struct nfsd_net *nn = shrink->private_data;
+
+	atomic_long_add(sc->nr_to_scan, &nn->nfsd_shrink_backlog);
+	queue_work(laundry_wq, &nn->nfsd_courtesy_work);
+
+	/*
+	 * The reaper runs from laundry_wq. Report no progress rather
+	 * than claim memory that is not free yet.
+	 */
+	return SHRINK_STOP;
+}
+
+static unsigned long
+nfsd4_deleg_shrinker_scan(struct shrinker *shrink, struct shrink_control *sc)
+{
+	struct nfsd_net *nn = shrink->private_data;
+
+	queue_work(laundry_wq, &nn->nfsd_deleg_work);
+
+	/*
+	 * The reaper sends CB_RECALL_ANY, so nothing is free when
+	 * this returns.
+	 */
 	return SHRINK_STOP;
 }
 
 static struct shrinker *
 nfsd4_alloc_state_shrinker(struct nfsd_net *nn, const char *name,
 			   unsigned long (*count)(struct shrinker *,
-						  struct shrink_control *))
+						  struct shrink_control *),
+			   unsigned long (*scan)(struct shrinker *,
+						 struct shrink_control *))
 {
 	struct shrinker *shrink;
 
@@ -5604,7 +5651,7 @@ nfsd4_alloc_state_shrinker(struct nfsd_net *nn, const char *name,
 		return NULL;
 
 	shrink->count_objects = count;
-	shrink->scan_objects = nfsd4_state_shrinker_scan;
+	shrink->scan_objects = scan;
 	shrink->private_data = nn;
 
 	shrinker_register(shrink);
@@ -7989,7 +8036,8 @@ deleg_reaper(struct nfsd_net *nn)
 			continue;
 		if (atomic_read(&clp->cl_delegs_in_recall))
 			continue;
-		if (ktime_get_boottime_seconds() - clp->cl_ra_time < 5)
+		if (ktime_get_boottime_seconds() - clp->cl_ra_time <
+				NFSD_RECALL_ANY_COOLDOWN_SECS)
 			continue;
 		if (clp->cl_cb_state != NFSD4_CB_UP)
 			continue;
@@ -8021,6 +8069,12 @@ deleg_reaper(struct nfsd_net *nn)
 		nfsd4_run_cb(&clp->cl_ra->ra_cb);
 	}
 	spin_unlock(&nn->client_lock);
+
+	/*
+	 * Stamp the sweep even when no recall went out. A sweep that
+	 * found nothing eligible finds nothing on an immediate retry.
+	 */
+	WRITE_ONCE(nn->nfsd_last_recall_any, ktime_get_boottime_seconds());
 }
 
 static void
@@ -8028,8 +8082,16 @@ nfsd4_courtesy_shrinker_worker(struct work_struct *work)
 {
 	struct nfsd_net *nn = container_of(work, struct nfsd_net,
 				nfsd_courtesy_work);
+	long backlog;
 
+	/*
+	 * Retire only the requests sampled here, so that requests
+	 * arriving while the reaper runs are still discounted by
+	 * nfsd4_courtesy_shrinker_count().
+	 */
+	backlog = atomic_long_read(&nn->nfsd_shrink_backlog);
 	courtesy_client_reaper(nn);
+	atomic_long_sub(backlog, &nn->nfsd_shrink_backlog);
 }
 
 static void
@@ -10002,17 +10064,21 @@ static int nfs4_state_create_net(struct net *net)
 	disable_delayed_work(&nn->laundromat_work);
 	INIT_WORK(&nn->nfsd_courtesy_work, nfsd4_courtesy_shrinker_worker);
 	INIT_WORK(&nn->nfsd_deleg_work, nfsd4_deleg_shrinker_worker);
+	atomic_long_set(&nn->nfsd_shrink_backlog, 0);
+	nn->nfsd_last_recall_any = 0;
 	get_net(net);
 
 	nn->nfsd_courtesy_shrinker =
 		nfsd4_alloc_state_shrinker(nn, "nfsd-courtesy",
-					   nfsd4_courtesy_shrinker_count);
+					   nfsd4_courtesy_shrinker_count,
+					   nfsd4_courtesy_shrinker_scan);
 	if (!nn->nfsd_courtesy_shrinker)
 		goto err_shrinker;
 
 	nn->nfsd_deleg_shrinker =
 		nfsd4_alloc_state_shrinker(nn, "nfsd-delegation",
-					   nfsd4_deleg_shrinker_count);
+					   nfsd4_deleg_shrinker_count,
+					   nfsd4_deleg_shrinker_scan);
 	if (!nn->nfsd_deleg_shrinker)
 		goto err_deleg_shrinker;
 

-- 
2.54.0
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.