nfsd: fix nfsd_file_unhash_and_dispose
[ Upstream commit 8d0d254b15cc5b7d46d85fb7ab8ecede9575e672 ] nfsd_file_unhash_and_dispose() is called for two reasons: We're either shutting down and purging the filecache, or we've gotten a notification about a file delete, so we want to go ahead and unhash it so that it'll get cleaned up when we close. We're either walking the hashtable or doing a lookup in it and we don't take a reference in either case. What we want to do in both cases is to try and unhash the object and put it on the dispose list if that was successful. If it's no longer hashed, then we don't want to touch it, with the assumption being that something else is already cleaning up the sentinel reference. Instead of trying to selectively decrement the refcount in this function, just unhash it, and if that was successful, move it to the dispose list. Then, the disposal routine will just clean that up as usual. Also, just make this a void function, drop the WARN_ON_ONCE, and the comments about deadlocking since the nature of the purported deadlock is no longer clear. Signed-off-by: Jeff Layton <jlayton@kernel.org> Signed-off-by: Chuck Lever <chuck.lever@oracle.com> Signed-off-by: Sasha Levin <sashal@kernel.org>
This commit is contained in:

committed by
Greg Kroah-Hartman

parent
683fb922e7
commit
2db3e73f9a
@@ -404,22 +404,15 @@ nfsd_file_unhash(struct nfsd_file *nf)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
static void
|
||||||
* Return true if the file was unhashed.
|
|
||||||
*/
|
|
||||||
static bool
|
|
||||||
nfsd_file_unhash_and_dispose(struct nfsd_file *nf, struct list_head *dispose)
|
nfsd_file_unhash_and_dispose(struct nfsd_file *nf, struct list_head *dispose)
|
||||||
{
|
{
|
||||||
trace_nfsd_file_unhash_and_dispose(nf);
|
trace_nfsd_file_unhash_and_dispose(nf);
|
||||||
if (!nfsd_file_unhash(nf))
|
if (nfsd_file_unhash(nf)) {
|
||||||
return false;
|
/* caller must call nfsd_file_dispose_list() later */
|
||||||
/* keep final reference for nfsd_file_lru_dispose */
|
nfsd_file_lru_remove(nf);
|
||||||
if (refcount_dec_not_one(&nf->nf_ref))
|
list_add(&nf->nf_lru, dispose);
|
||||||
return true;
|
}
|
||||||
|
|
||||||
nfsd_file_lru_remove(nf);
|
|
||||||
list_add(&nf->nf_lru, dispose);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -561,8 +554,6 @@ nfsd_file_dispose_list_delayed(struct list_head *dispose)
|
|||||||
* @lock: LRU list lock (unused)
|
* @lock: LRU list lock (unused)
|
||||||
* @arg: dispose list
|
* @arg: dispose list
|
||||||
*
|
*
|
||||||
* Note this can deadlock with nfsd_file_cache_purge.
|
|
||||||
*
|
|
||||||
* Return values:
|
* Return values:
|
||||||
* %LRU_REMOVED: @item was removed from the LRU
|
* %LRU_REMOVED: @item was removed from the LRU
|
||||||
* %LRU_ROTATE: @item is to be moved to the LRU tail
|
* %LRU_ROTATE: @item is to be moved to the LRU tail
|
||||||
@@ -747,8 +738,6 @@ nfsd_file_close_inode(struct inode *inode)
|
|||||||
*
|
*
|
||||||
* Walk the LRU list and close any entries that have not been used since
|
* Walk the LRU list and close any entries that have not been used since
|
||||||
* the last scan.
|
* the last scan.
|
||||||
*
|
|
||||||
* Note this can deadlock with nfsd_file_cache_purge.
|
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
nfsd_file_delayed_close(struct work_struct *work)
|
nfsd_file_delayed_close(struct work_struct *work)
|
||||||
@@ -890,16 +879,12 @@ out_err:
|
|||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Note this can deadlock with nfsd_file_lru_cb.
|
|
||||||
*/
|
|
||||||
static void
|
static void
|
||||||
__nfsd_file_cache_purge(struct net *net)
|
__nfsd_file_cache_purge(struct net *net)
|
||||||
{
|
{
|
||||||
struct rhashtable_iter iter;
|
struct rhashtable_iter iter;
|
||||||
struct nfsd_file *nf;
|
struct nfsd_file *nf;
|
||||||
LIST_HEAD(dispose);
|
LIST_HEAD(dispose);
|
||||||
bool del;
|
|
||||||
|
|
||||||
rhashtable_walk_enter(&nfsd_file_rhash_tbl, &iter);
|
rhashtable_walk_enter(&nfsd_file_rhash_tbl, &iter);
|
||||||
do {
|
do {
|
||||||
@@ -909,14 +894,7 @@ __nfsd_file_cache_purge(struct net *net)
|
|||||||
while (!IS_ERR_OR_NULL(nf)) {
|
while (!IS_ERR_OR_NULL(nf)) {
|
||||||
if (net && nf->nf_net != net)
|
if (net && nf->nf_net != net)
|
||||||
continue;
|
continue;
|
||||||
del = nfsd_file_unhash_and_dispose(nf, &dispose);
|
nfsd_file_unhash_and_dispose(nf, &dispose);
|
||||||
|
|
||||||
/*
|
|
||||||
* Deadlock detected! Something marked this entry as
|
|
||||||
* unhased, but hasn't removed it from the hash list.
|
|
||||||
*/
|
|
||||||
WARN_ON_ONCE(!del);
|
|
||||||
|
|
||||||
nf = rhashtable_walk_next(&iter);
|
nf = rhashtable_walk_next(&iter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user