From: Chuck Lever <cel@kernel.org>
To: Jeff Layton <jlayton@kernel.org>, NeilBrown <neil@brown.name>,
Olga Kornievskaia <okorniev@redhat.com>,
Dai Ngo <Dai.Ngo@oracle.com>, Tom Talpey <tom@talpey.com>
Cc: linux-nfs@vger.kernel.org, Chuck Lever <cel@kernel.org>
Subject: [PATCH v5 7/8] NFSD: Pace the state shrinker's scan requests
Date: Mon, 17 Aug 2026 21:08:42 -0400 [thread overview]
Message-ID: <20260817-recall-any-keep-count-v5-7-3b2cffce701e@kernel.org> (raw)
In-Reply-To: <20260817-recall-any-keep-count-v5-0-3b2cffce701e@kernel.org>
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 <cel@kernel.org>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
---
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
next prev parent reply other threads:[~2026-08-18 1:08 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-18 1:08 [PATCH v5 0/8] NFSD: CB_RECALL_ANY fixes and a meaningful keep count Chuck Lever
2026-08-18 1:08 ` [PATCH v5 1/8] NFSD: Do not send CB_RECALL_ANY to NFSv4.0 clients Chuck Lever
2026-08-18 1:08 ` [PATCH v5 2/8] NFSD: Count the delegations held by each client Chuck Lever
2026-08-18 1:08 ` [PATCH v5 3/8] NFSD: Name directory delegations in the CB_RECALL_ANY type mask Chuck Lever
2026-08-18 1:08 ` [PATCH v5 4/8] NFSD: Send a meaningful CB_RECALL_ANY keep count Chuck Lever
2026-08-18 1:08 ` [PATCH v5 5/8] NFSD: Count delegations per network namespace Chuck Lever
2026-08-18 1:08 ` [PATCH v5 6/8] NFSD: Give delegations their own state shrinker Chuck Lever
2026-08-18 1:08 ` Chuck Lever [this message]
2026-08-18 1:08 ` [PATCH v5 8/8] NFSD: Apportion CB_RECALL_ANY recalls among clients Chuck Lever
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=20260817-recall-any-keep-count-v5-7-3b2cffce701e@kernel.org \
--to=cel@kernel.org \
--cc=Dai.Ngo@oracle.com \
--cc=jlayton@kernel.org \
--cc=linux-nfs@vger.kernel.org \
--cc=neil@brown.name \
--cc=okorniev@redhat.com \
--cc=tom@talpey.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