summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChuck Lever <cel@kernel.org>2026-06-10 21:58:54 -0400
committerChuck Lever <cel@kernel.org>2026-07-05 21:07:22 -0400
commit5b83ddee37ac249d92eceed7661338e0652b1068 (patch)
treeb22054a6849adcf8b84a512d8a90d17133f26873
parent91a6b5f0b47e880ef63318739bca99622778cf42 (diff)
downloadlinux-next-5b83ddee37ac249d92eceed7661338e0652b1068.tar.gz
linux-next-5b83ddee37ac249d92eceed7661338e0652b1068.zip
NFSD: Count slot 0 in nfsd_total_target_slots
nfsd_total_target_slots sums "target_slots - 1" across sessions rather than the full target. Its sole consumer, the NFSv4.1 session slot shrinker's count callback, must report only reclaimable slots, and slot 0 is never reclaimable while a session is active. That correction is open-coded where a session's full target enters and leaves the counter, as "i - 1" on alloc and "from ?: 1" on free, and reads as an unexplained fudge. Give nfsd_total_target_slots the full-target meaning its name implies, and move the reclaimability correction to the single place that consumes it: nfsd_slot_count() subtracts nfsd_total_sessions, a new tally of the sessions on nfsd_session_list. One correction at the consumer is clearer than repeating it wherever a session's target enters or leaves the counter. The reclaimable figure the shrinker sees is unchanged: slot 0 was never reclaimable and still is not. The change only relocates the minus-slot-0 correction. Reviewed-by: NeilBrown <neil@brown.name> Reviewed-by: Jeff Layton <jlayton@kernel.org> Reviewed-by: Benjamin Coddington <bcodding@hammerspace.com> Link: https://patch.msgid.link/20260610-nfsd-slot-growth-clamp-v1-2-7b966700df0b@kernel.org Signed-off-by: Chuck Lever <cel@kernel.org>
-rw-r--r--fs/nfsd/nfs4state.c44
1 files changed, 32 insertions, 12 deletions
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index c88637406773..8b611c7b3d4d 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -2052,12 +2052,10 @@ gen_sessionid(struct nfsd4_session *ses)
static struct shrinker *nfsd_slot_shrinker;
static DEFINE_SPINLOCK(nfsd_session_list_lock);
static LIST_HEAD(nfsd_session_list);
-/* The sum of "target_slots-1" on every session. The shrinker can push this
- * down, though it can take a little while for the memory to actually
- * be freed. The "-1" is because we can never free slot 0 while the
- * session is active.
- */
+/* The sum of "target_slots" on every session, slot 0 included. */
static atomic_t nfsd_total_target_slots = ATOMIC_INIT(0);
+/* Session count, subtracted from the sum to exclude slot 0. */
+static atomic_t nfsd_total_sessions = ATOMIC_INIT(0);
static void
free_session_slots(struct nfsd4_session *ses, int from)
@@ -2081,9 +2079,11 @@ free_session_slots(struct nfsd4_session *ses, int from)
}
ses->se_fchannel.maxreqs = from;
if (ses->se_target_maxslots > from) {
- int new_target = from ?: 1;
- atomic_sub(ses->se_target_maxslots - new_target, &nfsd_total_target_slots);
- ses->se_target_maxslots = new_target;
+ int delta = ses->se_target_maxslots - from;
+
+ atomic_sub(delta, &nfsd_total_target_slots);
+ /* Retain one slot so the session can make forward progress. */
+ ses->se_target_maxslots = from ?: 1;
}
}
@@ -2179,7 +2179,7 @@ static struct nfsd4_session *alloc_session(struct nfsd4_channel_attrs *fattrs,
fattrs->maxreqs = i;
memcpy(&new->se_fchannel, fattrs, sizeof(struct nfsd4_channel_attrs));
new->se_target_maxslots = i;
- atomic_add(i - 1, &nfsd_total_target_slots);
+ atomic_add(i, &nfsd_total_target_slots);
new->se_cb_slot_avail = ~0U;
new->se_cb_highest_slot = min(battrs->maxreqs - 1,
NFSD_BC_SLOT_TABLE_SIZE - 1);
@@ -2307,9 +2307,17 @@ static void free_session(struct nfsd4_session *ses)
static unsigned long
nfsd_slot_count(struct shrinker *s, struct shrink_control *sc)
{
- unsigned long cnt = atomic_read(&nfsd_total_target_slots);
+ int count;
+
+ /*
+ * To prevent session deadlock, one slot of each session (slot 0)
+ * is not reclaimable while the session is active. Thus the number
+ * of sessions is subtracted from the total number of target slots.
+ */
+ count = atomic_read(&nfsd_total_target_slots) -
+ atomic_read(&nfsd_total_sessions);
- return cnt ? cnt : SHRINK_EMPTY;
+ return count > 0 ? count : SHRINK_EMPTY;
}
static unsigned long
@@ -2360,6 +2368,7 @@ static void init_session(struct svc_rqst *rqstp, struct nfsd4_session *new, stru
spin_lock(&nfsd_session_list_lock);
list_add_tail(&new->se_all_sessions, &nfsd_session_list);
+ atomic_inc(&nfsd_total_sessions);
spin_unlock(&nfsd_session_list_lock);
{
@@ -2433,6 +2442,7 @@ unhash_session(struct nfsd4_session *ses)
spin_unlock(&ses->se_client->cl_lock);
spin_lock(&nfsd_session_list_lock);
list_del(&ses->se_all_sessions);
+ atomic_dec(&nfsd_total_sessions);
spin_unlock(&nfsd_session_list_lock);
}
@@ -2581,7 +2591,17 @@ unhash_client_locked(struct nfs4_client *clp)
spin_lock(&nfsd_session_list_lock);
list_for_each_entry(ses, &clp->cl_sessions, se_perclnt) {
list_del_init(&ses->se_hash);
- list_del_init(&ses->se_all_sessions);
+ /*
+ * unhash_client_locked() can run more than once for a
+ * client; the session stays on cl_sessions across calls.
+ * The first pass empties se_all_sessions via
+ * list_del_init(), so skip the decrement on later passes
+ * to keep nfsd_total_sessions from being double-counted.
+ */
+ if (!list_empty(&ses->se_all_sessions)) {
+ list_del_init(&ses->se_all_sessions);
+ atomic_dec(&nfsd_total_sessions);
+ }
}
spin_unlock(&nfsd_session_list_lock);
spin_unlock(&clp->cl_lock);