ceph: drop negative child dentries before try pruning inode's alias
authorYan, Zheng <zyan@redhat.com>
Thu, 30 Nov 2017 03:59:22 +0000 (11:59 +0800)
committerIlya Dryomov <idryomov@gmail.com>
Fri, 8 Dec 2017 10:07:12 +0000 (11:07 +0100)
Negative child dentry holds reference on inode's alias, it makes
d_prune_aliases() do nothing.

Cc: stable@vger.kernel.org
Signed-off-by: "Yan, Zheng" <zyan@redhat.com>
Reviewed-by: Jeff Layton <jlayton@redhat.com>
Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
fs/ceph/mds_client.c

index ab69dcb..1b46825 100644 (file)
@@ -1440,6 +1440,29 @@ static int __close_session(struct ceph_mds_client *mdsc,
        return request_close_session(mdsc, session);
 }
 
+static bool drop_negative_children(struct dentry *dentry)
+{
+       struct dentry *child;
+       bool all_negative = true;
+
+       if (!d_is_dir(dentry))
+               goto out;
+
+       spin_lock(&dentry->d_lock);
+       list_for_each_entry(child, &dentry->d_subdirs, d_child) {
+               if (d_really_is_positive(child)) {
+                       all_negative = false;
+                       break;
+               }
+       }
+       spin_unlock(&dentry->d_lock);
+
+       if (all_negative)
+               shrink_dcache_parent(dentry);
+out:
+       return all_negative;
+}
+
 /*
  * Trim old(er) caps.
  *
@@ -1490,16 +1513,27 @@ static int trim_caps_cb(struct inode *inode, struct ceph_cap *cap, void *arg)
        if ((used | wanted) & ~oissued & mine)
                goto out;   /* we need these caps */
 
-       session->s_trim_caps--;
        if (oissued) {
                /* we aren't the only cap.. just remove us */
                __ceph_remove_cap(cap, true);
+               session->s_trim_caps--;
        } else {
+               struct dentry *dentry;
                /* try dropping referring dentries */
                spin_unlock(&ci->i_ceph_lock);
-               d_prune_aliases(inode);
-               dout("trim_caps_cb %p cap %p  pruned, count now %d\n",
-                    inode, cap, atomic_read(&inode->i_count));
+               dentry = d_find_any_alias(inode);
+               if (dentry && drop_negative_children(dentry)) {
+                       int count;
+                       dput(dentry);
+                       d_prune_aliases(inode);
+                       count = atomic_read(&inode->i_count);
+                       if (count == 1)
+                               session->s_trim_caps--;
+                       dout("trim_caps_cb %p cap %p pruned, count now %d\n",
+                            inode, cap, count);
+               } else {
+                       dput(dentry);
+               }
                return 0;
        }