NFSD: Give delegations their own state shrinker

Since commit 44df6f439a ("NFSD: add delegation reaper to react to
low memory condition"), nfsd_client_shrinker has managed two
unrelated populations of objects.

One population is courtesy clients. Shrinking that population can
be done synchronously and without risk of deadlock. The shrinker
callback could return a precise count of the number of objects
that were released.

The other population is delegations. Shrinking that population
requires sending a CB_RECALL_ANY; clients are not obligated to
return any delegation. The shrinker callback is structurally
unable to report progress.

What's more, the single shrinker callback falls back to
delegation reaping only when there are no courtesy clients left to
reclaim. A single courtesy client is enough to keep a namespace's
delegations out of the count it reports.

To begin to resolve these issues, refactor the existing state
shrinker into two: one for courtesy clients and one for reaping
delegations. Each manages the size of its own population, and the
shrinker names become namespace-specific.

Reviewed-by: Jeff Layton <jlayton@kernel.org>
Link: https://patch.msgid.link/20260817-recall-any-keep-count-v5-6-3b2cffce701e@kernel.org
Signed-off-by: Chuck Lever <cel@kernel.org>
This commit is contained in:
Chuck Lever 2026-08-17 21:08:41 -04:00
parent dda5168447
commit 51430fcf18
2 changed files with 65 additions and 18 deletions

View File

@ -240,8 +240,10 @@ struct nfsd_net {
atomic_t nfsd_courtesy_clients;
/* per-namespace; num_delegations in nfs4state.c is host-wide */
atomic_long_t nfsd_delegations;
struct shrinker *nfsd_client_shrinker;
struct work_struct nfsd_shrinker_work;
struct shrinker *nfsd_courtesy_shrinker;
struct shrinker *nfsd_deleg_shrinker;
struct work_struct nfsd_courtesy_work;
struct work_struct nfsd_deleg_work;
/* last time an admin-revoke happened for NFSv4.0 */
time64_t nfs40_last_revoke;

View File

@ -5579,16 +5579,27 @@ nfsd4_init_slabs(void)
}
static unsigned long
nfsd4_state_shrinker_count(struct shrinker *shrink, struct shrink_control *sc)
nfsd4_courtesy_shrinker_count(struct shrinker *shrink,
struct shrink_control *sc)
{
struct nfsd_net *nn = shrink->private_data;
long count;
count = atomic_read(&nn->nfsd_courtesy_clients);
if (!count)
count = atomic_long_read(&nn->nfsd_delegations);
if (count)
queue_work(laundry_wq, &nn->nfsd_shrinker_work);
queue_work(laundry_wq, &nn->nfsd_courtesy_work);
return (unsigned long)count;
}
static unsigned long
nfsd4_deleg_shrinker_count(struct shrinker *shrink, struct shrink_control *sc)
{
struct nfsd_net *nn = shrink->private_data;
long count;
count = atomic_long_read(&nn->nfsd_delegations);
if (count)
queue_work(laundry_wq, &nn->nfsd_deleg_work);
return (unsigned long)count;
}
@ -5598,6 +5609,25 @@ nfsd4_state_shrinker_scan(struct shrinker *shrink, struct shrink_control *sc)
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 shrinker *shrink;
shrink = shrinker_alloc(0, "%s:%s", name, nn->nfsd_name);
if (!shrink)
return NULL;
shrink->count_objects = count;
shrink->scan_objects = nfsd4_state_shrinker_scan;
shrink->private_data = nn;
shrinker_register(shrink);
return shrink;
}
void
nfsd4_init_leases_net(struct nfsd_net *nn)
{
@ -8011,12 +8041,20 @@ deleg_reaper(struct nfsd_net *nn)
}
static void
nfsd4_state_shrinker_worker(struct work_struct *work)
nfsd4_courtesy_shrinker_worker(struct work_struct *work)
{
struct nfsd_net *nn = container_of(work, struct nfsd_net,
nfsd_shrinker_work);
nfsd_courtesy_work);
courtesy_client_reaper(nn);
}
static void
nfsd4_deleg_shrinker_worker(struct work_struct *work)
{
struct nfsd_net *nn = container_of(work, struct nfsd_net,
nfsd_deleg_work);
deleg_reaper(nn);
}
@ -9979,21 +10017,26 @@ static int nfs4_state_create_net(struct net *net)
INIT_DELAYED_WORK(&nn->laundromat_work, laundromat_main);
/* Make sure this cannot run until client tracking is initialised */
disable_delayed_work(&nn->laundromat_work);
INIT_WORK(&nn->nfsd_shrinker_work, nfsd4_state_shrinker_worker);
INIT_WORK(&nn->nfsd_courtesy_work, nfsd4_courtesy_shrinker_worker);
INIT_WORK(&nn->nfsd_deleg_work, nfsd4_deleg_shrinker_worker);
get_net(net);
nn->nfsd_client_shrinker = shrinker_alloc(0, "nfsd-client");
if (!nn->nfsd_client_shrinker)
nn->nfsd_courtesy_shrinker =
nfsd4_alloc_state_shrinker(nn, "nfsd-courtesy",
nfsd4_courtesy_shrinker_count);
if (!nn->nfsd_courtesy_shrinker)
goto err_shrinker;
nn->nfsd_client_shrinker->scan_objects = nfsd4_state_shrinker_scan;
nn->nfsd_client_shrinker->count_objects = nfsd4_state_shrinker_count;
nn->nfsd_client_shrinker->private_data = nn;
shrinker_register(nn->nfsd_client_shrinker);
nn->nfsd_deleg_shrinker =
nfsd4_alloc_state_shrinker(nn, "nfsd-delegation",
nfsd4_deleg_shrinker_count);
if (!nn->nfsd_deleg_shrinker)
goto err_deleg_shrinker;
return 0;
err_deleg_shrinker:
shrinker_free(nn->nfsd_courtesy_shrinker);
err_shrinker:
put_net(net);
kfree(nn->sessionid_hashtbl);
@ -10094,8 +10137,10 @@ nfs4_state_shutdown_net(struct net *net)
struct list_head *pos, *next, reaplist;
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
shrinker_free(nn->nfsd_client_shrinker);
cancel_work_sync(&nn->nfsd_shrinker_work);
shrinker_free(nn->nfsd_courtesy_shrinker);
shrinker_free(nn->nfsd_deleg_shrinker);
cancel_work_sync(&nn->nfsd_courtesy_work);
cancel_work_sync(&nn->nfsd_deleg_work);
disable_delayed_work_sync(&nn->laundromat_work);
locks_end_grace(&nn->nfsd4_manager);