[PATCH] nfsd: convert global state_lock to per-net deleg_lock

From: Jeff Layton

Date: Tue Feb 24 2026 - 08:31:37 EST


Replace the global state_lock spinlock with a per-nfsd_net deleg_lock.
The state_lock was only used to protect delegation lifecycle operations
(the del_recall_lru list and delegation hash/unhash), all of which are
scoped to a single network namespace. Making the lock per-net removes
a source of unnecessary contention between containers.

Signed-off-by: Jeff Layton <jlayton@xxxxxxxxxx>
---
fs/nfsd/netns.h | 3 +++
fs/nfsd/nfs4state.c | 62 +++++++++++++++++++++++++++--------------------------
fs/nfsd/state.h | 2 +-
3 files changed, 36 insertions(+), 31 deletions(-)

diff --git a/fs/nfsd/netns.h b/fs/nfsd/netns.h
index 0071cc25fbc23282d928c6cb9a8c5468c4560c33..71193dd96b58680172cc6b55f8eaf8fe437a3323 100644
--- a/fs/nfsd/netns.h
+++ b/fs/nfsd/netns.h
@@ -101,6 +101,9 @@ struct nfsd_net {
struct list_head close_lru;
struct list_head del_recall_lru;

+ /* protects del_recall_lru and delegation hash/unhash */
+ spinlock_t deleg_lock;
+
/* protected by blocked_locks_lock */
struct list_head blocked_locks_lru;

diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index 1d31f2bb21622680e04238be861d4040da7d2f01..ba49f49bb93b9f590a0afa9910c7c2058f9b11e4 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -93,13 +93,6 @@ static void deleg_reaper(struct nfsd_net *nn);

/* Locking: */

-/*
- * Currently used for the del_recall_lru and file hash table. In an
- * effort to decrease the scope of the client_mutex, this spinlock may
- * eventually cover more:
- */
-static DEFINE_SPINLOCK(state_lock);
-
enum nfsd4_st_mutex_lock_subclass {
OPEN_STATEID_MUTEX = 0,
LOCK_STATEID_MUTEX = 1,
@@ -1295,8 +1288,9 @@ nfs4_delegation_exists(struct nfs4_client *clp, struct nfs4_file *fp)
{
struct nfs4_delegation *searchdp = NULL;
struct nfs4_client *searchclp = NULL;
+ struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);

- lockdep_assert_held(&state_lock);
+ lockdep_assert_held(&nn->deleg_lock);
lockdep_assert_held(&fp->fi_lock);

list_for_each_entry(searchdp, &fp->fi_delegations, dl_perfile) {
@@ -1325,8 +1319,9 @@ static int
hash_delegation_locked(struct nfs4_delegation *dp, struct nfs4_file *fp)
{
struct nfs4_client *clp = dp->dl_stid.sc_client;
+ struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);

- lockdep_assert_held(&state_lock);
+ lockdep_assert_held(&nn->deleg_lock);
lockdep_assert_held(&fp->fi_lock);
lockdep_assert_held(&clp->cl_lock);

@@ -1348,8 +1343,10 @@ static bool
unhash_delegation_locked(struct nfs4_delegation *dp, unsigned short statusmask)
{
struct nfs4_file *fp = dp->dl_stid.sc_file;
+ struct nfsd_net *nn = net_generic(dp->dl_stid.sc_client->net,
+ nfsd_net_id);

- lockdep_assert_held(&state_lock);
+ lockdep_assert_held(&nn->deleg_lock);

if (!delegation_hashed(dp))
return false;
@@ -1374,10 +1371,12 @@ unhash_delegation_locked(struct nfs4_delegation *dp, unsigned short statusmask)
static void destroy_delegation(struct nfs4_delegation *dp)
{
bool unhashed;
+ struct nfsd_net *nn = net_generic(dp->dl_stid.sc_client->net,
+ nfsd_net_id);

- spin_lock(&state_lock);
+ spin_lock(&nn->deleg_lock);
unhashed = unhash_delegation_locked(dp, SC_STATUS_CLOSED);
- spin_unlock(&state_lock);
+ spin_unlock(&nn->deleg_lock);
if (unhashed)
destroy_unhashed_deleg(dp);
}
@@ -1840,11 +1839,11 @@ void nfsd4_revoke_states(struct nfsd_net *nn, struct super_block *sb)
case SC_TYPE_DELEG:
refcount_inc(&stid->sc_count);
dp = delegstateid(stid);
- spin_lock(&state_lock);
+ spin_lock(&nn->deleg_lock);
if (!unhash_delegation_locked(
dp, SC_STATUS_ADMIN_REVOKED))
dp = NULL;
- spin_unlock(&state_lock);
+ spin_unlock(&nn->deleg_lock);
if (dp)
revoke_delegation(dp);
break;
@@ -2510,13 +2509,13 @@ __destroy_client(struct nfs4_client *clp)
struct nfs4_delegation *dp;
LIST_HEAD(reaplist);

- spin_lock(&state_lock);
+ spin_lock(&nn->deleg_lock);
while (!list_empty(&clp->cl_delegations)) {
dp = list_entry(clp->cl_delegations.next, struct nfs4_delegation, dl_perclnt);
unhash_delegation_locked(dp, SC_STATUS_CLOSED);
list_add(&dp->dl_recall_lru, &reaplist);
}
- spin_unlock(&state_lock);
+ spin_unlock(&nn->deleg_lock);
while (!list_empty(&reaplist)) {
dp = list_entry(reaplist.next, struct nfs4_delegation, dl_recall_lru);
list_del_init(&dp->dl_recall_lru);
@@ -5427,12 +5426,12 @@ static void nfsd4_cb_recall_prepare(struct nfsd4_callback *cb)
* If the dl_time != 0, then we know that it has already been
* queued for a lease break. Don't queue it again.
*/
- spin_lock(&state_lock);
+ spin_lock(&nn->deleg_lock);
if (delegation_hashed(dp) && dp->dl_time == 0) {
dp->dl_time = ktime_get_boottime_seconds();
list_add_tail(&dp->dl_recall_lru, &nn->del_recall_lru);
}
- spin_unlock(&state_lock);
+ spin_unlock(&nn->deleg_lock);
}

static int nfsd4_cb_recall_done(struct nfsd4_callback *cb,
@@ -6064,6 +6063,7 @@ nfs4_set_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp,
{
bool deleg_ts = nfsd4_want_deleg_timestamps(open);
struct nfs4_client *clp = stp->st_stid.sc_client;
+ struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);
struct nfs4_file *fp = stp->st_stid.sc_file;
struct nfs4_clnt_odstate *odstate = stp->st_clnt_odstate;
struct nfs4_delegation *dp;
@@ -6123,7 +6123,7 @@ nfs4_set_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp,
return ERR_PTR(-EOPNOTSUPP);
}

- spin_lock(&state_lock);
+ spin_lock(&nn->deleg_lock);
spin_lock(&fp->fi_lock);
if (nfs4_delegation_exists(clp, fp))
status = -EAGAIN;
@@ -6138,7 +6138,7 @@ nfs4_set_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp,
} else
fp->fi_delegees++;
spin_unlock(&fp->fi_lock);
- spin_unlock(&state_lock);
+ spin_unlock(&nn->deleg_lock);
if (nf)
nfsd_file_put(nf);
if (status)
@@ -6182,13 +6182,13 @@ nfs4_set_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp,
if (fp->fi_had_conflict)
goto out_unlock;

- spin_lock(&state_lock);
+ spin_lock(&nn->deleg_lock);
spin_lock(&clp->cl_lock);
spin_lock(&fp->fi_lock);
status = hash_delegation_locked(dp, fp);
spin_unlock(&fp->fi_lock);
spin_unlock(&clp->cl_lock);
- spin_unlock(&state_lock);
+ spin_unlock(&nn->deleg_lock);

if (status)
goto out_unlock;
@@ -6964,7 +6964,7 @@ nfs4_laundromat(struct nfsd_net *nn)

nfs40_clean_admin_revoked(nn, &lt);

- spin_lock(&state_lock);
+ spin_lock(&nn->deleg_lock);
list_for_each_safe(pos, next, &nn->del_recall_lru) {
dp = list_entry (pos, struct nfs4_delegation, dl_recall_lru);
if (!state_expired(&lt, dp->dl_time))
@@ -6973,7 +6973,7 @@ nfs4_laundromat(struct nfsd_net *nn)
unhash_delegation_locked(dp, SC_STATUS_REVOKED);
list_add(&dp->dl_recall_lru, &reaplist);
}
- spin_unlock(&state_lock);
+ spin_unlock(&nn->deleg_lock);
while (!list_empty(&reaplist)) {
dp = list_first_entry(&reaplist, struct nfs4_delegation,
dl_recall_lru);
@@ -8996,6 +8996,7 @@ static int nfs4_state_create_net(struct net *net)
INIT_LIST_HEAD(&nn->client_lru);
INIT_LIST_HEAD(&nn->close_lru);
INIT_LIST_HEAD(&nn->del_recall_lru);
+ spin_lock_init(&nn->deleg_lock);
spin_lock_init(&nn->client_lock);
spin_lock_init(&nn->s2s_cp_lock);
idr_init(&nn->s2s_cp_stateids);
@@ -9127,13 +9128,13 @@ nfs4_state_shutdown_net(struct net *net)
locks_end_grace(&nn->nfsd4_manager);

INIT_LIST_HEAD(&reaplist);
- spin_lock(&state_lock);
+ spin_lock(&nn->deleg_lock);
list_for_each_safe(pos, next, &nn->del_recall_lru) {
dp = list_entry (pos, struct nfs4_delegation, dl_recall_lru);
unhash_delegation_locked(dp, SC_STATUS_CLOSED);
list_add(&dp->dl_recall_lru, &reaplist);
}
- spin_unlock(&state_lock);
+ spin_unlock(&nn->deleg_lock);
list_for_each_safe(pos, next, &reaplist) {
dp = list_entry (pos, struct nfs4_delegation, dl_recall_lru);
list_del_init(&dp->dl_recall_lru);
@@ -9456,6 +9457,7 @@ nfsd_get_dir_deleg(struct nfsd4_compound_state *cstate,
struct nfsd_file *nf)
{
struct nfs4_client *clp = cstate->clp;
+ struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);
struct nfs4_delegation *dp;
struct file_lease *fl;
struct nfs4_file *fp, *rfp;
@@ -9479,7 +9481,7 @@ nfsd_get_dir_deleg(struct nfsd4_compound_state *cstate,
}

/* if this client already has one, return that it's unavailable */
- spin_lock(&state_lock);
+ spin_lock(&nn->deleg_lock);
spin_lock(&fp->fi_lock);
/* existing delegation? */
if (nfs4_delegation_exists(clp, fp)) {
@@ -9491,7 +9493,7 @@ nfsd_get_dir_deleg(struct nfsd4_compound_state *cstate,
++fp->fi_delegees;
}
spin_unlock(&fp->fi_lock);
- spin_unlock(&state_lock);
+ spin_unlock(&nn->deleg_lock);

if (status) {
put_nfs4_file(fp);
@@ -9520,13 +9522,13 @@ nfsd_get_dir_deleg(struct nfsd4_compound_state *cstate,
* trying to set a delegation on the same file. If that happens,
* then just say UNAVAIL.
*/
- spin_lock(&state_lock);
+ spin_lock(&nn->deleg_lock);
spin_lock(&clp->cl_lock);
spin_lock(&fp->fi_lock);
status = hash_delegation_locked(dp, fp);
spin_unlock(&fp->fi_lock);
spin_unlock(&clp->cl_lock);
- spin_unlock(&state_lock);
+ spin_unlock(&nn->deleg_lock);

if (!status) {
put_nfs4_file(fp);
diff --git a/fs/nfsd/state.h b/fs/nfsd/state.h
index 4a246b07dee0f88af2cd7383d46329512363ff83..07528004989edc4c09694ae97aa0d5ed0e6dd5cd 100644
--- a/fs/nfsd/state.h
+++ b/fs/nfsd/state.h
@@ -123,7 +123,7 @@ struct nfs4_stid {
#define SC_TYPE_LAYOUT BIT(3)
unsigned short sc_type;

-/* state_lock protects sc_status for delegation stateids.
+/* nn->deleg_lock protects sc_status for delegation stateids.
* ->cl_lock protects sc_status for open and lock stateids.
* ->st_mutex also protect sc_status for open stateids.
* ->ls_lock protects sc_status for layout stateids.

---
base-commit: 9affc4c96f49744c7eab647d922fc82d290834b2
change-id: 20260224-nfsd-deleg-lock-45d00443c33d

Best regards,
--
Jeff Layton <jlayton@xxxxxxxxxx>