FAILED: patch "[PATCH] nfsd: dedup nfs4_client_to_reclaim inserts" failed to apply to 6.18-stable tree
Subsystems (2)
[email protected]
maintainerThe patch below does not apply to the 6.18-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.18.y
git checkout FETCH_HEAD
git cherry-pick -x 9b57793863f69c608691c98e57c2f9be2db9dfed
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026090325-overpass-emoticon-993a@gregkh' --subject-prefix 'PATCH 6.18.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 9b57793863f69c608691c98e57c2f9be2db9dfed Mon Sep 17 00:00:00 2001
From: Jeff Layton <[email protected]>
Date: Sat, 30 May 2026 09:19:20 -0400
Subject: [PATCH] nfsd: dedup nfs4_client_to_reclaim inserts
nfs4_client_to_reclaim() unconditionally allocates a new nfs4_client_reclaim, prepends it to reclaim_str_hashtbl[], and bumps reclaim_str_hashtbl_size with no check for an existing entry for the same client name. After a reboot with a populated recovery directory that inflates the counter by one for every client that reclaims:
boot: load_recdir()
nfs4_client_to_reclaim(name) /* entry #1, size++ */
grace: RECLAIM_COMPLETE
__nfsd4_create_reclaim_record_grace()
nfs4_client_to_reclaim(name) /* entry #2, size++ */inc_reclaim_complete() ends the grace period early only when
atomic_inc_return(&nn->nr_reclaim_complete) ==
nn->reclaim_str_hashtbl_sizeWith reclaim_str_hashtbl_size at 2N and nr_reclaim_complete capped at N, the equality never holds and the fast end-of-grace path is dead. The grace period always runs out the full 90-second laundromat timer, and the shadow entry left in the hash table carries a dangling cr_clp for any reader that walks it.
Fix nfs4_client_to_reclaim() to look the name up with nfsd4_find_reclaim_client() first and, on a hit, fold the new princhash into the existing record (if it lacks one) and return that record without allocating or touching reclaim_str_hashtbl_size. On kmemdup() failure during the fold-in, return NULL so __cld_pipe_inprogress_downcall() surfaces -EFAULT to nfsdcld, matching the miss-path contract.
Add an rw_semaphore (reclaim_str_hashtbl_lock) to struct nfsd_net that serialises all access to reclaim_str_hashtbl[] and reclaim_str_hashtbl_size. Writers (nfs4_client_to_reclaim, nfs4_remove_reclaim_record callers) hold the write side; readers (nfsd4_cld_check*, inc_reclaim_complete, clients_still_reclaiming, nfs4_has_reclaimed_state, nfsd4_check_legacy_client) hold the read side. All call sites are in sleepable context, and none is a hot path, so the rwsem cost is negligible.
fs/nfsd/netns.h +5 -1
@@ -93,6 +93,7 @@ struct nfsd_net {*/ struct list_head *reclaim_str_hashtbl;int reclaim_str_hashtbl_size; +struct rw_semaphore reclaim_str_hashtbl_lock;struct list_head *conf_id_hashtbl;struct rb_root conf_name_tree;struct list_head *unconf_id_hashtbl;
@@ -105,7 +106,10 @@ struct nfsd_net {* close_lru holds (open) stateowner queue ordered by nfs4_stateowner.so_time * for last close replay. * -* All of the above fields are protected by the client_mutex. +* reclaim_str_hashtbl[], reclaim_str_hashtbl_size are protected by +* reclaim_str_hashtbl_lock. +* +* All of the remaining fields are protected by the client_lock. */ struct list_head client_lru;struct list_head close_lru;
fs/nfsd/nfs4recover.c +29 -7
@@ -285,10 +285,12 @@ __nfsd4_remove_reclaim_record_grace(const char *dname, int len,return ; } name.len = len; +down_write (&nn->reclaim_str_hashtbl_lock); crp =nfsd4_find_reclaim_client (name, nn); -kfree (name.data);if (crp)nfs4_remove_reclaim_record (crp, nn); +up_write (&nn->reclaim_str_hashtbl_lock); +kfree (name.data); } static void
@@ -484,6 +486,7 @@ nfs4_legacy_state_init(struct net *net)for (i =0 ; i <CLIENT_HASH_SIZE ; i++)INIT_LIST_HEAD (&nn->reclaim_str_hashtbl[i]); nn->reclaim_str_hashtbl_size =0 ; +init_rwsem (&nn->reclaim_str_hashtbl_lock);return 0 ; }
@@ -598,13 +601,16 @@ nfsd4_check_legacy_client(struct nfs4_client *clp) goto out_enoent; } name.len =HEXDIR_LEN ; +down_read (&nn->reclaim_str_hashtbl_lock); crp =nfsd4_find_reclaim_client (name, nn); -kfree (name.data);if (crp) {set_bit (NFSD4_CLIENT_STABLE , &clp->cl_flags); crp->cr_clp = clp; -return 0 ; } +up_read (&nn->reclaim_str_hashtbl_lock); +kfree (name.data); +if (crp) +return 0 ; out_enoent:return -ENOENT ;
@@ -1176,6 +1182,7 @@ nfsd4_cld_check(struct nfs4_client *clp)return 0 ;/* look for it in the reclaim hashtable otherwise */ +down_read (&nn->reclaim_str_hashtbl_lock); crp =nfsd4_find_reclaim_client (clp->cl_name, nn);if (crp) goto found;
@@ -1191,6 +1198,7 @@ nfsd4_cld_check(struct nfs4_client *clp)if (!name.data) {dprintk ("%s: failed to allocate memory for name.data!\n" , __func__); +up_read (&nn->reclaim_str_hashtbl_lock);return -ENOENT ; } name.len =HEXDIR_LEN ;
@@ -1201,9 +1209,11 @@ nfsd4_cld_check(struct nfs4_client *clp) }#endif +up_read (&nn->reclaim_str_hashtbl_lock);return -ENOENT ; found: crp->cr_clp = clp; +up_read (&nn->reclaim_str_hashtbl_lock);return 0 ; }
@@ -1215,6 +1225,7 @@ nfsd4_cld_check_v2(struct nfs4_client *clp)struct cld_net *cn = nn->cld_net;#endif struct nfs4_client_reclaim *crp; +unsigned int princhashlen;char *principal =NULL ;/* did we already find that this client is stable? */
@@ -1222,6 +1233,7 @@ nfsd4_cld_check_v2(struct nfs4_client *clp)return 0 ;/* look for it in the reclaim hashtable otherwise */ +down_read (&nn->reclaim_str_hashtbl_lock); crp =nfsd4_find_reclaim_client (clp->cl_name, nn);if (crp) goto found;
@@ -1237,6 +1249,7 @@ nfsd4_cld_check_v2(struct nfs4_client *clp)if (!name.data) {dprintk ("%s: failed to allocate memory for name.data\n" , __func__); +up_read (&nn->reclaim_str_hashtbl_lock);return -ENOENT ; } name.len =HEXDIR_LEN ;
@@ -1247,23 +1260,31 @@ nfsd4_cld_check_v2(struct nfs4_client *clp) }#endif +up_read (&nn->reclaim_str_hashtbl_lock);return -ENOENT ; found: -if (crp->cr_princhash.len) { + princhashlen = crp->cr_princhash.len; +if (princhashlen) {u8 digest[SHA256_DIGEST_SIZE ]; +u8 *pdata;if (clp->cl_cred.cr_raw_principal) principal = clp->cl_cred.cr_raw_principal;else if (clp->cl_cred.cr_principal) principal = clp->cl_cred.cr_principal; -if (principal ==NULL ) +if (principal ==NULL ) { +up_read (&nn->reclaim_str_hashtbl_lock);return -ENOENT ; + }sha256 (principal,strlen (principal), digest); -if (memcmp (crp->cr_princhash.data, digest, - crp->cr_princhash.len)) + pdata = crp->cr_princhash.data; +if (memcmp (pdata, digest, princhashlen)) { +up_read (&nn->reclaim_str_hashtbl_lock);return -ENOENT ; + } } crp->cr_clp = clp; +up_read (&nn->reclaim_str_hashtbl_lock);return 0 ; }
@@ -1362,6 +1383,7 @@ nfs4_cld_state_init(struct net *net)for (i =0 ; i <CLIENT_HASH_SIZE ; i++)INIT_LIST_HEAD (&nn->reclaim_str_hashtbl[i]); nn->reclaim_str_hashtbl_size =0 ; +init_rwsem (&nn->reclaim_str_hashtbl_lock);set_bit (NFSD_NET_TRACK_RECLAIM_COMPLETES , &nn->flags);atomic_set (&nn->nr_reclaim_complete,0 );
fs/nfsd/nfs4state.c +55 -6
122 lines not on this page · raw message
Sasha Levin
maintainerPATCH 1/3 nfsd: use workqueue enable/disable APIs for v4_end_grace sync
From: NeilBrown <[email protected]>
[ Upstream commit 27e383ddeb3c88e20874c545285f2dce1a98f56a ]
"nfsd: provide locking for v4_end_grace" introduced a client_tracking_active flag protected by nn->client_lock to prevent the laundromat from being scheduled before client tracking initialization or after shutdown begins. That commit is suitable for backporting to LTS kernels that predate commit 86898fa6b8cd ("workqueue: Implement disable/enable for (delayed) work items").
However, the workqueue subsystem in recent kernels provides enable_delayed_work() and disable_delayed_work_sync() for this purpose. Using this mechanism enable us to remove the client_tracking_active flag and associated spinlock operations while preserving the same synchronization guarantees, which is a cleaner long-term approach.
Stable-dep-of: 9b57793863f6 ("nfsd: dedup nfs4_client_to_reclaim inserts")
--- fs/nfsd/netns.h | 1 - fs/nfsd/nfs4state.c | 22 +++++++++------------- 2 files changed, 9 insertions(+), 14 deletions(-)
fs/nfsd/netns.h +0 -1
@@ -67,7 +67,6 @@ struct nfsd_net {struct lock_manager nfsd4_manager;bool grace_ended;bool grace_end_forced; -bool client_tracking_active;time64_t boot_time;struct dentry *nfsd_client_dir;
fs/nfsd/nfs4state.c +9 -13
@@ -6653,14 +6653,14 @@ bool nfsd4_force_end_grace(struct nfsd_net *nn) {if (!nn->client_tracking_ops)return false; -spin_lock (&nn->client_lock); -if (nn->grace_ended || !nn->client_tracking_active) { -spin_unlock (&nn->client_lock); +if (READ_ONCE (nn->grace_ended))return false; - } +/* laundromat_work must be initialised now, though it might be disabled */ WRITE_ONCE (nn->grace_end_forced, true); +/* mod_delayed_work() doesn't queue work after +* nfs4_state_shutdown_net() has called disable_delayed_work_sync() +*/ mod_delayed_work (laundry_wq, &nn->laundromat_work,0 ); -spin_unlock (&nn->client_lock);return true; }
@@ -8995,7 +8995,6 @@ static int nfs4_state_create_net(struct net *net) nn->boot_time =ktime_get_real_seconds (); nn->grace_ended = false; nn->grace_end_forced = false; - nn->client_tracking_active = false; nn->nfsd4_manager.block_opens = true;INIT_LIST_HEAD (&nn->nfsd4_manager.list);INIT_LIST_HEAD (&nn->client_lru);
@@ -9010,6 +9009,8 @@ static int nfs4_state_create_net(struct net *net)INIT_LIST_HEAD (&nn->blocked_locks_lru);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);get_net (net);
@@ -9077,9 +9078,7 @@ nfs4_state_start_net(struct net *net)locks_start_grace (net, &nn->nfsd4_manager);nfsd4_client_tracking_init (net);/* safe for laundromat to run now */ -spin_lock (&nn->client_lock); - nn->client_tracking_active = true; -spin_unlock (&nn->client_lock); +enable_delayed_work (&nn->laundromat_work);if (nn->track_reclaim_completes && nn->reclaim_str_hashtbl_size ==0 ) goto skip_grace; printk(KERN_INFO "NFSD: starting %lld-second grace period (net %x)\n",
@@ -9128,10 +9127,7 @@ nfs4_state_shutdown_net(struct net *net)shrinker_free (nn->nfsd_client_shrinker);cancel_work_sync (&nn->nfsd_shrinker_work); -spin_lock (&nn->client_lock); - nn->client_tracking_active = false; -spin_unlock (&nn->client_lock); -cancel_delayed_work_sync (&nn->laundromat_work); +disable_delayed_work_sync (&nn->laundromat_work);locks_end_grace (&nn->nfsd4_manager);INIT_LIST_HEAD (&reaplist);
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 2/3 nfsd: convert nfsd_net boolean flags to unsigned long flags word
From: Chris Mason <[email protected]>
[ Upstream commit 11a5fe42e1811f793e04ef885b639ea7668f439d ]
nfsd_net contains several boolean fields that are accessed from concurrent contexts without serialization. In particular, nfsd4_end_grace() guards its drain path with a plain bool:
if (nn->grace_ended)
return;
nn->grace_ended = true;The read and the write are independent, and nothing in struct nfsd_net serializes them. At least two contexts can reach this code with no lock held:
laundromat path
laundry_wq kworker
nfs4_laundromat()
nfsd4_end_grace()
RECLAIM_COMPLETE path
nfsd compound kthread
nfsd4_reclaim_complete()
inc_reclaim_complete()
nfsd4_end_grace()Both callers can observe grace_ended == false on different CPUs, both store true, and both proceed into nfsd4_record_grace_done(), which invokes the active client_tracking_ops->grace_done callback. For tracking ops that drain reclaim_str_hashtbl (legacy_tracking_ops via nfsd4_recdir_purge_old, and the cld v1+ ops via nfsd4_cld_grace_done), grace_done calls nfs4_release_reclaim(), which walks every bucket of reclaim_str_hashtbl with no lock and calls nfs4_remove_reclaim_record() (list_del + kfree) on each entry. Two concurrent walkers corrupt the list and double-free every nfs4_client_reclaim. A concurrent nfsd4_find_reclaim_client() iterating the same bucket reads through freed memory.
A third call site exists in nfs4_state_start_net() on the skip_grace startup path, but it runs under nfsd_mutex before any client has connected and before the laundromat's first delayed work fires, so it cannot race with the two callers above.
Replace the scattered boolean fields in nfsd_net with a single unsigned long flags word and an enum nfsd_net_flag for the bit positions. The grace_ended race is fixed by using test_and_set_bit(), which is atomic on all architectures. The remaining flags (grace_end_forced, in_grace, somebody_reclaimed, track_reclaim_completes, nfsd_net_up, lockd_up) are converted to use test_bit/set_bit/clear_bit for consistency. This avoids sub-word cmpxchg issues on architectures like Hexagon that only support word-sized atomic operations.
Stable-dep-of: 9b57793863f6 ("nfsd: dedup nfs4_client_to_reclaim inserts")
--- fs/nfsd/netns.h | 19 +++++++++++-------- fs/nfsd/nfs4proc.c | 2 +- fs/nfsd/nfs4recover.c | 12 ++++++------ fs/nfsd/nfs4state.c | 40 ++++++++++++++++++++++++---------------- fs/nfsd/nfsctl.c | 2 +- fs/nfsd/nfssvc.c | 22 +++++++++++----------- 6 files changed, 54 insertions(+), 43 deletions(-)
fs/nfsd/netns.h +11 -8
@@ -27,6 +27,16 @@struct cld_net ;struct nfsd4_client_tracking_ops ; +enum nfsd_net_flag { +NFSD_NET_GRACE_ENDED , +NFSD_NET_GRACE_END_FORCED , +NFSD_NET_IN_GRACE , +NFSD_NET_SOMEBODY_RECLAIMED , +NFSD_NET_TRACK_RECLAIM_COMPLETES , +NFSD_NET_UP , +NFSD_NET_LOCKD_UP , +}; +enum {/* cache misses due only to checksum comparison failures */ NFSD_STATS_PAYLOAD_MISSES ,
@@ -65,8 +75,7 @@ struct nfsd_net {struct cache_detail *nametoid_cache;struct lock_manager nfsd4_manager; -bool grace_ended; -bool grace_end_forced; +unsigned long flags;time64_t boot_time;struct dentry *nfsd_client_dir;
@@ -113,19 +122,13 @@ struct nfsd_net {spinlock_t blocked_locks_lock;struct file *rec_file; -bool in_grace;const struct nfsd4_client_tracking_ops *client_tracking_ops;time64_t nfsd4_lease;time64_t nfsd4_grace; -bool somebody_reclaimed; -bool track_reclaim_completes;atomic_t nr_reclaim_complete; -bool nfsd_net_up; -bool lockd_up; -seqlock_t writeverf_lock;unsigned char writeverf[8 ];
fs/nfsd/nfs4proc.c +1 -1
@@ -631,7 +631,7 @@ nfsd4_open(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,pr_warn ("nfsd4_process_open2 failed to open newly-created file: status=%u\n" ,be32_to_cpu (status));if (reclaim && !status) - nn->somebody_reclaimed = true; +set_bit (NFSD_NET_SOMEBODY_RECLAIMED , &nn->flags); out:if (open->op_filp) {fput (open->op_filp);
fs/nfsd/nfs4recover.c +6 -6
@@ -211,7 +211,7 @@ nfsd4_create_clid_dir(struct nfs4_client *clp) out_unlock:inode_unlock (d_inode (dir));if (status ==0 ) { -if (nn->in_grace) +if (test_bit (NFSD_NET_IN_GRACE , &nn->flags))__nfsd4_create_reclaim_record_grace (clp, dname, nn);vfs_fsync (nn->rec_file,0 ); }else {
@@ -371,7 +371,7 @@ nfsd4_remove_clid_dir(struct nfs4_client *clp)nfs4_reset_creds (original_cred);if (status ==0 ) {vfs_fsync (nn->rec_file,0 ); -if (nn->in_grace) +if (test_bit (NFSD_NET_IN_GRACE , &nn->flags))__nfsd4_remove_reclaim_record_grace (dname,HEXDIR_LEN , nn); }
@@ -429,7 +429,7 @@ nfsd4_recdir_purge_old(struct nfsd_net *nn) {int status; - nn->in_grace = false; +clear_bit (NFSD_NET_IN_GRACE , &nn->flags);if (!nn->rec_file)return ; status =mnt_want_write_file (nn->rec_file);
@@ -511,7 +511,7 @@ nfsd4_init_recdir(struct net *net)nfs4_reset_creds (original_cred);if (!status) - nn->in_grace = true; +set_bit (NFSD_NET_IN_GRACE , &nn->flags);return status; }
@@ -1431,7 +1431,7 @@ nfs4_cld_state_init(struct net *net)for (i =0 ; i <CLIENT_HASH_SIZE ; i++)INIT_LIST_HEAD (&nn->reclaim_str_hashtbl[i]); nn->reclaim_str_hashtbl_size =0 ; - nn->track_reclaim_completes = true; +set_bit (NFSD_NET_TRACK_RECLAIM_COMPLETES , &nn->flags);atomic_set (&nn->nr_reclaim_complete,0 );return 0 ;
@@ -1442,7 +1442,7 @@ nfs4_cld_state_shutdown(struct net *net) {struct nfsd_net *nn =net_generic (net, nfsd_net_id); - nn->track_reclaim_completes = false; +clear_bit (NFSD_NET_TRACK_RECLAIM_COMPLETES , &nn->flags);kfree (nn->reclaim_str_hashtbl); }
fs/nfsd/nfs4state.c +24 -16
@@ -2553,7 +2553,7 @@ static void inc_reclaim_complete(struct nfs4_client *clp) {struct nfsd_net *nn =net_generic (clp->net, nfsd_net_id); -if (!nn->track_reclaim_completes) +if (!test_bit (NFSD_NET_TRACK_RECLAIM_COMPLETES , &nn->flags))return ;if (!nfsd4_find_reclaim_client (clp->cl_name, nn))return ;
@@ -4918,8 +4918,6 @@ nfsd4_init_leases_net(struct nfsd_net *nn) nn->nfsd4_lease =90 ;/* default lease time */ nn->nfsd4_grace =90 ; - nn->somebody_reclaimed = false; - nn->track_reclaim_completes = false; nn->clverifier_counter =get_random_u32 (); nn->clientid_base =get_random_u32 (); nn->clientid_counter = nn->clientid_base +1 ;
@@ -6607,12 +6605,21 @@ nfsd4_renew(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,static void nfsd4_end_grace (struct nfsd_net *nn) { -/* do nothing if grace period already ended */ -if (nn->grace_ended) +/* +* nfsd4_end_grace() can be entered concurrently from the +* laundromat workqueue and from an nfsd compound thread +* handling RECLAIM_COMPLETE. Without serialization, both +* callers can observe NFSD_NET_GRACE_ENDED clear and proceed +* into nfsd4_record_grace_done(). For tracking ops whose +* grace_done drains reclaim_str_hashtbl, that results in +* list corruption and a double free of every +* nfs4_client_reclaim entry. Use an atomic test-and-set so +* exactly one caller proceeds. +*/ +if (test_and_set_bit (NFSD_NET_GRACE_ENDED , &nn->flags))return ;trace_nfsd_grace_complete (nn); - nn->grace_ended = true;/* * If the server goes down again right now, an NFSv4 * client will still be allowed to reclaim after it comes back up,
@@ -6653,10 +6660,10 @@ bool nfsd4_force_end_grace(struct nfsd_net *nn) {if (!nn->client_tracking_ops)return false; -if (READ_ONCE (nn->grace_ended)) +if (test_bit (NFSD_NET_GRACE_ENDED , &nn->flags))return false;/* laundromat_work must be initialised now, though it might be disabled */ -WRITE_ONCE (nn->grace_end_forced, true); +set_bit (NFSD_NET_GRACE_END_FORCED , &nn->flags);/* mod_delayed_work() doesn't queue work after * nfs4_state_shutdown_net() has called disable_delayed_work_sync() */
@@ -6673,15 +6680,15 @@ static bool clients_still_reclaiming(struct nfsd_net *nn)time64_t double_grace_period_end = nn->boot_time +2 * nn->nfsd4_lease; -if (READ_ONCE (nn->grace_end_forced)) +if (test_bit (NFSD_NET_GRACE_END_FORCED , &nn->flags))return false; -if (nn->track_reclaim_completes && +if (test_bit (NFSD_NET_TRACK_RECLAIM_COMPLETES , &nn->flags) &&atomic_read (&nn->nr_reclaim_complete) == nn->reclaim_str_hashtbl_size)return false; -if (!nn->somebody_reclaimed) +if (!test_bit (NFSD_NET_SOMEBODY_RECLAIMED , &nn->flags))return false; - nn->somebody_reclaimed = false; +clear_bit (NFSD_NET_SOMEBODY_RECLAIMED , &nn->flags);/* * If we've given them *two* lease times to reclaim, and they're * still not done, give up:
@@ -8462,7 +8469,7 @@ nfsd4_lock(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,nfs4_inc_and_copy_stateid (&lock->lk_resp_stateid, &lock_stp->st_stid); status =0 ;if (lock->lk_reclaim) - nn->somebody_reclaimed = true; +set_bit (NFSD_NET_SOMEBODY_RECLAIMED , &nn->flags);break ;case FILE_LOCK_DEFERRED :kref_put (&nbl->nbl_kref, free_nbl);
@@ -8993,8 +9000,8 @@ static int nfs4_state_create_net(struct net *net) nn->conf_name_tree =RB_ROOT ; nn->unconf_name_tree =RB_ROOT ; nn->boot_time =ktime_get_real_seconds (); - nn->grace_ended = false; - nn->grace_end_forced = false; +clear_bit (NFSD_NET_GRACE_ENDED , &nn->flags); +clear_bit (NFSD_NET_GRACE_END_FORCED , &nn->flags); nn->nfsd4_manager.block_opens = true;INIT_LIST_HEAD (&nn->nfsd4_manager.list);INIT_LIST_HEAD (&nn->client_lru);
@@ -9079,7 +9086,8 @@ nfs4_state_start_net(struct net *net)nfsd4_client_tracking_init (net);/* safe for laundromat to run now */ enable_delayed_work (&nn->laundromat_work); -if (nn->track_reclaim_completes && nn->reclaim_str_hashtbl_size ==0 ) +if (test_bit (NFSD_NET_TRACK_RECLAIM_COMPLETES , &nn->flags) && + nn->reclaim_str_hashtbl_size ==0 ) goto skip_grace;printk (KERN_INFO "NFSD: starting %lld-second grace period (net %x)\n" , nn->nfsd4_grace, net->ns.inum);
fs/nfsd/nfsctl.c +1 -1
@@ -1109,7 +1109,7 @@ static ssize_t write_v4_end_grace(struct file *file, char *buf, size_t size) }return scnprintf (buf,SIMPLE_TRANSACTION_LIMIT ,"%c\n" , - nn->grace_ended ?'Y' :'N' ); +test_bit (NFSD_NET_GRACE_ENDED , &nn->flags) ?'Y' :'N' ); }#endif
fs/nfsd/nfssvc.c +11 -11
@@ -371,7 +371,7 @@ static int nfsd_startup_net(struct net *net, const struct cred *cred)struct nfsd_net *nn =net_generic (net, nfsd_net_id);int ret; -if (nn->nfsd_net_up) +if (test_bit (NFSD_NET_UP , &nn->flags))return 0 ; ret =nfsd_startup_generic ();
@@ -381,11 +381,11 @@ static int nfsd_startup_net(struct net *net, const struct cred *cred)if (ret) goto out_socks; -if (nfsd_needs_lockd (nn) && !nn->lockd_up) { +if (nfsd_needs_lockd (nn) && !test_bit (NFSD_NET_LOCKD_UP , &nn->flags)) { ret =lockd_up (net, cred);if (ret) goto out_socks; - nn->lockd_up = true; +set_bit (NFSD_NET_LOCKD_UP , &nn->flags); } ret =nfsd_file_cache_start_net (net);
@@ -403,7 +403,7 @@ static int nfsd_startup_net(struct net *net, const struct cred *cred)if (ret) goto out_reply_cache; - nn->nfsd_net_up = true; +set_bit (NFSD_NET_UP , &nn->flags);return 0 ; out_reply_cache:
@@ -411,9 +411,9 @@ static int nfsd_startup_net(struct net *net, const struct cred *cred) out_filecache:nfsd_file_cache_shutdown_net (net); out_lockd: -if (nn->lockd_up) { +if (test_bit (NFSD_NET_LOCKD_UP , &nn->flags)) {lockd_down (net); - nn->lockd_up = false; +clear_bit (NFSD_NET_LOCKD_UP , &nn->flags); } out_socks:nfsd_shutdown_generic ();
@@ -424,7 +424,7 @@ static void nfsd_shutdown_net(struct net *net) {struct nfsd_net *nn =net_generic (net, nfsd_net_id); -if (nn->nfsd_net_up) { +if (test_bit (NFSD_NET_UP , &nn->flags)) {percpu_ref_kill_and_confirm (&nn->nfsd_net_ref, nfsd_net_done);wait_for_completion (&nn->nfsd_net_confirm_done);
@@ -432,18 +432,18 @@ static void nfsd_shutdown_net(struct net *net)nfs4_state_shutdown_net (net);nfsd_reply_cache_shutdown (nn);nfsd_file_cache_shutdown_net (net); -if (nn->lockd_up) { +if (test_bit (NFSD_NET_LOCKD_UP , &nn->flags)) {lockd_down (net); - nn->lockd_up = false; +clear_bit (NFSD_NET_LOCKD_UP , &nn->flags); }wait_for_completion (&nn->nfsd_net_free_done); }percpu_ref_exit (&nn->nfsd_net_ref); -if (nn->nfsd_net_up) +if (test_bit (NFSD_NET_UP , &nn->flags))nfsd_shutdown_generic (); - nn->nfsd_net_up = false; +clear_bit (NFSD_NET_UP , &nn->flags); }static DEFINE_SPINLOCK (nfsd_notifier_lock);
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 3/3 nfsd: dedup nfs4_client_to_reclaim inserts
In reply to Sasha LevinFrom: Jeff Layton <[email protected]>
[ Upstream commit 9b57793863f69c608691c98e57c2f9be2db9dfed ]
nfs4_client_to_reclaim() unconditionally allocates a new nfs4_client_reclaim, prepends it to reclaim_str_hashtbl[], and bumps reclaim_str_hashtbl_size with no check for an existing entry for the same client name. After a reboot with a populated recovery directory that inflates the counter by one for every client that reclaims:
boot: load_recdir()
nfs4_client_to_reclaim(name) /* entry #1, size++ */
grace: RECLAIM_COMPLETE
__nfsd4_create_reclaim_record_grace()
nfs4_client_to_reclaim(name) /* entry #2, size++ */inc_reclaim_complete() ends the grace period early only when
atomic_inc_return(&nn->nr_reclaim_complete) ==
nn->reclaim_str_hashtbl_sizeWith reclaim_str_hashtbl_size at 2N and nr_reclaim_complete capped at N, the equality never holds and the fast end-of-grace path is dead. The grace period always runs out the full 90-second laundromat timer, and the shadow entry left in the hash table carries a dangling cr_clp for any reader that walks it.
Fix nfs4_client_to_reclaim() to look the name up with nfsd4_find_reclaim_client() first and, on a hit, fold the new princhash into the existing record (if it lacks one) and return that record without allocating or touching reclaim_str_hashtbl_size. On kmemdup() failure during the fold-in, return NULL so __cld_pipe_inprogress_downcall() surfaces -EFAULT to nfsdcld, matching the miss-path contract.
Add an rw_semaphore (reclaim_str_hashtbl_lock) to struct nfsd_net that serialises all access to reclaim_str_hashtbl[] and reclaim_str_hashtbl_size. Writers (nfs4_client_to_reclaim, nfs4_remove_reclaim_record callers) hold the write side; readers (nfsd4_cld_check*, inc_reclaim_complete, clients_still_reclaiming, nfs4_has_reclaimed_state, nfsd4_check_legacy_client) hold the read side. All call sites are in sleepable context, and none is a hot path, so the rwsem cost is negligible.
--- fs/nfsd/netns.h | 6 ++++- fs/nfsd/nfs4recover.c | 36 ++++++++++++++++++++----- fs/nfsd/nfs4state.c | 61 ++++++++++++++++++++++++++++++++++++++----- 3 files changed, 89 insertions(+), 14 deletions(-)
fs/nfsd/netns.h +5 -1
@@ -92,6 +92,7 @@ struct nfsd_net {*/ struct list_head *reclaim_str_hashtbl;int reclaim_str_hashtbl_size; +struct rw_semaphore reclaim_str_hashtbl_lock;struct list_head *conf_id_hashtbl;struct rb_root conf_name_tree;struct list_head *unconf_id_hashtbl;
@@ -104,7 +105,10 @@ struct nfsd_net {* close_lru holds (open) stateowner queue ordered by nfs4_stateowner.so_time * for last close replay. * -* All of the above fields are protected by the client_mutex. +* reclaim_str_hashtbl[], reclaim_str_hashtbl_size are protected by +* reclaim_str_hashtbl_lock. +* +* All of the remaining fields are protected by the client_lock. */ struct list_head client_lru;struct list_head close_lru;
fs/nfsd/nfs4recover.c +29 -7
@@ -337,10 +337,12 @@ __nfsd4_remove_reclaim_record_grace(const char *dname, int len,return ; } name.len = len; +down_write (&nn->reclaim_str_hashtbl_lock); crp =nfsd4_find_reclaim_client (name, nn); -kfree (name.data);if (crp)nfs4_remove_reclaim_record (crp, nn); +up_write (&nn->reclaim_str_hashtbl_lock); +kfree (name.data); } static void
@@ -541,6 +543,7 @@ nfs4_legacy_state_init(struct net *net)for (i =0 ; i <CLIENT_HASH_SIZE ; i++)INIT_LIST_HEAD (&nn->reclaim_str_hashtbl[i]); nn->reclaim_str_hashtbl_size =0 ; +init_rwsem (&nn->reclaim_str_hashtbl_lock);return 0 ; }
@@ -660,13 +663,16 @@ nfsd4_check_legacy_client(struct nfs4_client *clp) goto out_enoent; } name.len =HEXDIR_LEN ; +down_read (&nn->reclaim_str_hashtbl_lock); crp =nfsd4_find_reclaim_client (name, nn); -kfree (name.data);if (crp) {set_bit (NFSD4_CLIENT_STABLE , &clp->cl_flags); crp->cr_clp = clp; -return 0 ; } +up_read (&nn->reclaim_str_hashtbl_lock); +kfree (name.data); +if (crp) +return 0 ; out_enoent:return -ENOENT ;
@@ -1238,6 +1244,7 @@ nfsd4_cld_check(struct nfs4_client *clp)return 0 ;/* look for it in the reclaim hashtable otherwise */ +down_read (&nn->reclaim_str_hashtbl_lock); crp =nfsd4_find_reclaim_client (clp->cl_name, nn);if (crp) goto found;
@@ -1256,6 +1263,7 @@ nfsd4_cld_check(struct nfs4_client *clp)if (!name.data) {dprintk ("%s: failed to allocate memory for name.data!\n" , __func__); +up_read (&nn->reclaim_str_hashtbl_lock);return -ENOENT ; } name.len =HEXDIR_LEN ;
@@ -1266,9 +1274,11 @@ nfsd4_cld_check(struct nfs4_client *clp) }#endif +up_read (&nn->reclaim_str_hashtbl_lock);return -ENOENT ; found: crp->cr_clp = clp; +up_read (&nn->reclaim_str_hashtbl_lock);return 0 ; }
@@ -1280,6 +1290,7 @@ nfsd4_cld_check_v2(struct nfs4_client *clp)struct cld_net *cn = nn->cld_net;#endif struct nfs4_client_reclaim *crp; +unsigned int princhashlen;char *principal =NULL ;/* did we already find that this client is stable? */
@@ -1287,6 +1298,7 @@ nfsd4_cld_check_v2(struct nfs4_client *clp)return 0 ;/* look for it in the reclaim hashtable otherwise */ +down_read (&nn->reclaim_str_hashtbl_lock); crp =nfsd4_find_reclaim_client (clp->cl_name, nn);if (crp) goto found;
@@ -1305,6 +1317,7 @@ nfsd4_cld_check_v2(struct nfs4_client *clp)if (!name.data) {dprintk ("%s: failed to allocate memory for name.data\n" , __func__); +up_read (&nn->reclaim_str_hashtbl_lock);return -ENOENT ; } name.len =HEXDIR_LEN ;
@@ -1315,23 +1328,31 @@ nfsd4_cld_check_v2(struct nfs4_client *clp) }#endif +up_read (&nn->reclaim_str_hashtbl_lock);return -ENOENT ; found: -if (crp->cr_princhash.len) { + princhashlen = crp->cr_princhash.len; +if (princhashlen) {u8 digest[SHA256_DIGEST_SIZE ]; +u8 *pdata;if (clp->cl_cred.cr_raw_principal) principal = clp->cl_cred.cr_raw_principal;else if (clp->cl_cred.cr_principal) principal = clp->cl_cred.cr_principal; -if (principal ==NULL ) +if (principal ==NULL ) { +up_read (&nn->reclaim_str_hashtbl_lock);return -ENOENT ; + }sha256 (principal,strlen (principal), digest); -if (memcmp (crp->cr_princhash.data, digest, - crp->cr_princhash.len)) + pdata = crp->cr_princhash.data; +if (memcmp (pdata, digest, princhashlen)) { +up_read (&nn->reclaim_str_hashtbl_lock);return -ENOENT ; + } } crp->cr_clp = clp; +up_read (&nn->reclaim_str_hashtbl_lock);return 0 ; }
@@ -1431,6 +1452,7 @@ nfs4_cld_state_init(struct net *net)for (i =0 ; i <CLIENT_HASH_SIZE ; i++)INIT_LIST_HEAD (&nn->reclaim_str_hashtbl[i]); nn->reclaim_str_hashtbl_size =0 ; +init_rwsem (&nn->reclaim_str_hashtbl_lock);set_bit (NFSD_NET_TRACK_RECLAIM_COMPLETES , &nn->flags);atomic_set (&nn->nr_reclaim_complete,0 );
fs/nfsd/nfs4state.c +55 -6
122 lines not on this page · raw message
Signature · 1 line
2.53.0
Greg KH
maintainerRe: [PATCH 6.18.y 1/3] nfsd: use workqueue enable/disable APIs for v4_end_grace sync
In reply to Sasha LevinQuoted 109 lines · Sasha Levin
From: NeilBrown <[email protected]>
[ Upstream commit
27e383ddeb3c88e20874c545285f2dce1a98f56a]"nfsd: provide locking for
v4_end_grace" introduced aclient_tracking_activeflag protected bynn->client_lockto prevent the laundromat from being scheduled before client tracking initialization or after shutdown begins. That commit is suitable for backporting to LTS kernels that predate commit86898fa6b8cd("workqueue: Implement disable/enable for (delayed) work items").However, the workqueue subsystem in recent kernels provides
enable_delayed_work()anddisable_delayed_work_sync()for this purpose. Using this mechanism enable us to remove theclient_tracking_activeflag and associated spinlock operations while preserving the same synchronization guarantees, which is a cleaner long-term approach.Signed-off-by NeilBrown <[email protected]>Tested-by Li Lingfeng <[email protected]>Reviewed-by Jeff Layton <[email protected]>Signed-off-by Chuck Lever <[email protected]>Stable-dep-of:
9b57793863f6("nfsd: dedupnfs4_client_to_reclaiminserts")Signed-off-by Sasha Levin <[email protected]>--- fs/nfsd/netns.h | 1 - fs/nfsd/nfs4state.c | 22 +++++++++------------- 2 files changed, 9 insertions(+), 14 deletions(-)fs/nfsd/netns.h +0
-1@@ -67,7 +67,6 @@ struct nfsd_net {struct lock_manager nfsd4_manager;bool grace_ended;bool grace_end_forced; -bool client_tracking_active;time64_t boot_time;struct dentry *nfsd_client_dir;fs/nfsd/nfs4state.c +9
-13@@ -6653,14 +6653,14 @@ bool nfsd4_force_end_grace(struct nfsd_net *nn) {if (!nn->client_tracking_ops)return false; -spin_lock (&nn->client_lock); -if (nn->grace_ended || !nn->client_tracking_active) { -spin_unlock (&nn->client_lock); +if (READ_ONCE (nn->grace_ended))return false; - } +/* laundromat_work must be initialised now, though it might be disabled */ WRITE_ONCE (nn->grace_end_forced, true); +/* mod_delayed_work() doesn't queue work after +* nfs4_state_shutdown_net() has called disable_delayed_work_sync() +*/ mod_delayed_work (laundry_wq, &nn->laundromat_work,0 ); -spin_unlock (&nn->client_lock);return true; }@@ -8995,7 +8995,6 @@ static int nfs4_state_create_net(struct net *net) nn->boot_time =ktime_get_real_seconds (); nn->grace_ended = false; nn->grace_end_forced = false; - nn->client_tracking_active = false; nn->nfsd4_manager.block_opens = true;INIT_LIST_HEAD (&nn->nfsd4_manager.list);INIT_LIST_HEAD (&nn->client_lru);@@ -9010,6 +9009,8 @@ static int nfs4_state_create_net(struct net *net)INIT_LIST_HEAD (&nn->blocked_locks_lru);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);get_net (net);@@ -9077,9 +9078,7 @@ nfs4_state_start_net(struct net *net)locks_start_grace (net, &nn->nfsd4_manager);nfsd4_client_tracking_init (net);/* safe for laundromat to run now */ -spin_lock (&nn->client_lock); - nn->client_tracking_active = true; -spin_unlock (&nn->client_lock); +enable_delayed_work (&nn->laundromat_work);if (nn->track_reclaim_completes && nn->reclaim_str_hashtbl_size ==0 ) goto skip_grace; printk(KERN_INFO "NFSD: starting %lld-second grace period (net %x)\n",@@ -9128,10 +9127,7 @@ nfs4_state_shutdown_net(struct net *net)shrinker_free (nn->nfsd_client_shrinker);cancel_work_sync (&nn->nfsd_shrinker_work); -spin_lock (&nn->client_lock); - nn->client_tracking_active = false; -spin_unlock (&nn->client_lock); -cancel_delayed_work_sync (&nn->laundromat_work); +disable_delayed_work_sync (&nn->laundromat_work);locks_end_grace (&nn->nfsd4_manager);INIT_LIST_HEAD (&reaplist);Signature · 1 line
2.53.0
This series breaks the build :(