]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
nfs: don't invalidate dentries on transient errors
authorScott Mayhew <smayhew@redhat.com>
Thu, 23 May 2024 19:01:22 +0000 (15:01 -0400)
committerTrond Myklebust <trond.myklebust@hammerspace.com>
Fri, 24 May 2024 16:31:11 +0000 (12:31 -0400)
This is a slight variation on a patch previously proposed by Neil Brown
that never got merged.

Prior to commit 5ceb9d7fdaaf ("NFS: Refactor nfs_lookup_revalidate()"),
any error from nfs_lookup_verify_inode() other than -ESTALE would result
in nfs_lookup_revalidate() returning that error (-ESTALE is mapped to
zero).

Since that commit, all errors result in nfs_lookup_revalidate()
returning zero, resulting in dentries being invalidated where they
previously were not (particularly in the case of -ERESTARTSYS).

Fix it by passing the actual error code to nfs_lookup_revalidate_done(),
and leaving the decision on whether to  map the error code to zero or
one to nfs_lookup_revalidate_done().

A simple reproducer is to run the following python code in a
subdirectory of an NFS mount (not in the root of the NFS mount):

---8<---
import os
import multiprocessing
import time

if __name__=="__main__":
    multiprocessing.set_start_method("spawn")

    count = 0
    while True:
        try:
            os.getcwd()
            pool = multiprocessing.Pool(10)
            pool.close()
            pool.terminate()
            count += 1
        except Exception as e:
            print(f"Failed after {count} iterations")
            print(e)
            break
---8<---

Prior to commit 5ceb9d7fdaaf, the above code would run indefinitely.
After commit 5ceb9d7fdaaf, it fails almost immediately with -ENOENT.

Signed-off-by: Scott Mayhew <smayhew@redhat.com>
Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
fs/nfs/dir.c

index 3429309962267fb83a5a1712a122b0e9942ada6f..788077a4feb9a6412ec15c74415e0cdf0d260cb0 100644 (file)
@@ -1627,7 +1627,16 @@ nfs_lookup_revalidate_done(struct inode *dir, struct dentry *dentry,
        switch (error) {
        case 1:
                break;
-       case 0:
+       case -ETIMEDOUT:
+               if (inode && (IS_ROOT(dentry) ||
+                             NFS_SERVER(inode)->flags & NFS_MOUNT_SOFTREVAL))
+                       error = 1;
+               break;
+       case -ESTALE:
+       case -ENOENT:
+               error = 0;
+               fallthrough;
+       default:
                /*
                 * We can't d_drop the root of a disconnected tree:
                 * its d_hash is on the s_anon list and d_drop() would hide
@@ -1682,18 +1691,8 @@ static int nfs_lookup_revalidate_dentry(struct inode *dir,
 
        dir_verifier = nfs_save_change_attribute(dir);
        ret = NFS_PROTO(dir)->lookup(dir, dentry, fhandle, fattr);
-       if (ret < 0) {
-               switch (ret) {
-               case -ESTALE:
-               case -ENOENT:
-                       ret = 0;
-                       break;
-               case -ETIMEDOUT:
-                       if (NFS_SERVER(inode)->flags & NFS_MOUNT_SOFTREVAL)
-                               ret = 1;
-               }
+       if (ret < 0)
                goto out;
-       }
 
        /* Request help from readdirplus */
        nfs_lookup_advise_force_readdirplus(dir, flags);
@@ -1737,7 +1736,7 @@ nfs_do_lookup_revalidate(struct inode *dir, struct dentry *dentry,
                         unsigned int flags)
 {
        struct inode *inode;
-       int error;
+       int error = 0;
 
        nfs_inc_stats(dir, NFSIOS_DENTRYREVALIDATE);
        inode = d_inode(dentry);
@@ -1782,7 +1781,7 @@ out_valid:
 out_bad:
        if (flags & LOOKUP_RCU)
                return -ECHILD;
-       return nfs_lookup_revalidate_done(dir, dentry, inode, 0);
+       return nfs_lookup_revalidate_done(dir, dentry, inode, error);
 }
 
 static int