Merge branch 'work.dcache2' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
[sfrench/cifs-2.6.git] / fs / dcache.c
index f41121e5d1ec09462b425a7bbff004e788e26fd3..e88cf0554e65907d0136595a4521fc06fd521da4 100644 (file)
@@ -861,6 +861,32 @@ void dput(struct dentry *dentry)
 }
 EXPORT_SYMBOL(dput);
 
+static void __dput_to_list(struct dentry *dentry, struct list_head *list)
+__must_hold(&dentry->d_lock)
+{
+       if (dentry->d_flags & DCACHE_SHRINK_LIST) {
+               /* let the owner of the list it's on deal with it */
+               --dentry->d_lockref.count;
+       } else {
+               if (dentry->d_flags & DCACHE_LRU_LIST)
+                       d_lru_del(dentry);
+               if (!--dentry->d_lockref.count)
+                       d_shrink_add(dentry, list);
+       }
+}
+
+void dput_to_list(struct dentry *dentry, struct list_head *list)
+{
+       rcu_read_lock();
+       if (likely(fast_dput(dentry))) {
+               rcu_read_unlock();
+               return;
+       }
+       rcu_read_unlock();
+       if (!retain_dentry(dentry))
+               __dput_to_list(dentry, list);
+       spin_unlock(&dentry->d_lock);
+}
 
 /* This must be called with d_lock held */
 static inline void __dget_dlock(struct dentry *dentry)
@@ -1067,7 +1093,7 @@ out:
        return false;
 }
 
-static void shrink_dentry_list(struct list_head *list)
+void shrink_dentry_list(struct list_head *list)
 {
        while (!list_empty(list)) {
                struct dentry *dentry, *parent;
@@ -1089,18 +1115,9 @@ static void shrink_dentry_list(struct list_head *list)
                rcu_read_unlock();
                d_shrink_del(dentry);
                parent = dentry->d_parent;
+               if (parent != dentry)
+                       __dput_to_list(parent, list);
                __dentry_kill(dentry);
-               if (parent == dentry)
-                       continue;
-               /*
-                * We need to prune ancestors too. This is necessary to prevent
-                * quadratic behavior of shrink_dcache_parent(), but is also
-                * expected to be beneficial in reducing dentry cache
-                * fragmentation.
-                */
-               dentry = parent;
-               while (dentry && !lockref_put_or_lock(&dentry->d_lockref))
-                       dentry = dentry_kill(dentry);
        }
 }
 
@@ -1445,8 +1462,11 @@ out:
 
 struct select_data {
        struct dentry *start;
+       union {
+               long found;
+               struct dentry *victim;
+       };
        struct list_head dispose;
-       int found;
 };
 
 static enum d_walk_ret select_collect(void *_data, struct dentry *dentry)
@@ -1478,6 +1498,37 @@ out:
        return ret;
 }
 
+static enum d_walk_ret select_collect2(void *_data, struct dentry *dentry)
+{
+       struct select_data *data = _data;
+       enum d_walk_ret ret = D_WALK_CONTINUE;
+
+       if (data->start == dentry)
+               goto out;
+
+       if (dentry->d_flags & DCACHE_SHRINK_LIST) {
+               if (!dentry->d_lockref.count) {
+                       rcu_read_lock();
+                       data->victim = dentry;
+                       return D_WALK_QUIT;
+               }
+       } else {
+               if (dentry->d_flags & DCACHE_LRU_LIST)
+                       d_lru_del(dentry);
+               if (!dentry->d_lockref.count)
+                       d_shrink_add(dentry, &data->dispose);
+       }
+       /*
+        * We can return to the caller if we have found some (this
+        * ensures forward progress). We'll be coming back to find
+        * the rest.
+        */
+       if (!list_empty(&data->dispose))
+               ret = need_resched() ? D_WALK_QUIT : D_WALK_NORETRY;
+out:
+       return ret;
+}
+
 /**
  * shrink_dcache_parent - prune dcache
  * @parent: parent of entries to prune
@@ -1487,12 +1538,9 @@ out:
 void shrink_dcache_parent(struct dentry *parent)
 {
        for (;;) {
-               struct select_data data;
+               struct select_data data = {.start = parent};
 
                INIT_LIST_HEAD(&data.dispose);
-               data.start = parent;
-               data.found = 0;
-
                d_walk(parent, &data, select_collect);
 
                if (!list_empty(&data.dispose)) {
@@ -1503,6 +1551,24 @@ void shrink_dcache_parent(struct dentry *parent)
                cond_resched();
                if (!data.found)
                        break;
+               data.victim = NULL;
+               d_walk(parent, &data, select_collect2);
+               if (data.victim) {
+                       struct dentry *parent;
+                       spin_lock(&data.victim->d_lock);
+                       if (!shrink_lock_dentry(data.victim)) {
+                               spin_unlock(&data.victim->d_lock);
+                               rcu_read_unlock();
+                       } else {
+                               rcu_read_unlock();
+                               parent = data.victim->d_parent;
+                               if (parent != data.victim)
+                                       __dput_to_list(parent, &data.dispose);
+                               __dentry_kill(data.victim);
+                       }
+               }
+               if (!list_empty(&data.dispose))
+                       shrink_dentry_list(&data.dispose);
        }
 }
 EXPORT_SYMBOL(shrink_dcache_parent);