]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
nfsd: convert global state_lock to per-net deleg_lock
authorJeff Layton <jlayton@kernel.org>
Tue, 24 Feb 2026 13:28:11 +0000 (08:28 -0500)
committerChuck Lever <chuck.lever@oracle.com>
Mon, 30 Mar 2026 01:25:09 +0000 (21:25 -0400)
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@kernel.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
fs/nfsd/netns.h
fs/nfsd/nfs4state.c
fs/nfsd/state.h

index 9fa6006026585cc35fc3c28ec09941f0078f7b74..3a89d4708e8a9aa2e7ab3f84b81882eaa3b21446 100644 (file)
@@ -99,6 +99,9 @@ struct nfsd_net {
         */
        struct list_head client_lru;
        struct list_head close_lru;
+
+       /* protects del_recall_lru and delegation hash/unhash */
+       spinlock_t deleg_lock ____cacheline_aligned;
        struct list_head del_recall_lru;
 
        /* protected by blocked_locks_lock */
index 1d31f2bb21622680e04238be861d4040da7d2f01..ba49f49bb93b9f590a0afa9910c7c2058f9b11e4 100644 (file)
@@ -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);
index ec1c5467012ecc881e479e1a0441c726c198721a..3159c7b67f503d59cadfd1f9bdb7823e5e7e6bf2 100644 (file)
@@ -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.