Commit 3b3f09f4 authored by Al Viro's avatar Al Viro

get rid of trylock loop in locking dentries on shrink list

In case of trylock failure don't re-add to the list - drop the locks
and carefully get them in the right order.  For shrink_dentry_list(),
somebody having grabbed a reference to dentry means that we can
kick it off-list, so if we find dentry being modified under us we
don't need to play silly buggers with retries anyway - off the list
it is.

The locking logics taken out into a helper of its own; lock_parent()
is no longer used for dentries that can be killed under us.

[fix from Eric Biggers folded]
Signed-off-by: default avatarAl Viro <viro@zeniv.linux.org.uk>
parent c19457f0
...@@ -974,56 +974,86 @@ void d_prune_aliases(struct inode *inode) ...@@ -974,56 +974,86 @@ void d_prune_aliases(struct inode *inode)
} }
EXPORT_SYMBOL(d_prune_aliases); EXPORT_SYMBOL(d_prune_aliases);
static void shrink_dentry_list(struct list_head *list) /*
* Lock a dentry from shrink list.
* Note that dentry is *not* protected from concurrent dentry_kill(),
* d_delete(), etc. It is protected from freeing (by the fact of
* being on a shrink list), but everything else is fair game.
* Return false if dentry has been disrupted or grabbed, leaving
* the caller to kick it off-list. Otherwise, return true and have
* that dentry's inode and parent both locked.
*/
static bool shrink_lock_dentry(struct dentry *dentry)
{ {
struct dentry *dentry, *parent;
while (!list_empty(list)) {
struct inode *inode; struct inode *inode;
dentry = list_entry(list->prev, struct dentry, d_lru); struct dentry *parent;
spin_lock(&dentry->d_lock);
parent = lock_parent(dentry);
/* if (dentry->d_lockref.count)
* The dispose list is isolated and dentries are not accounted return false;
* to the LRU here, so we can simply remove it from the list
* here regardless of whether it is referenced or not.
*/
d_shrink_del(dentry);
/* inode = dentry->d_inode;
* We found an inuse dentry which was not removed from if (inode && unlikely(!spin_trylock(&inode->i_lock))) {
* the LRU because of laziness during lookup. Do not free it. rcu_read_lock(); /* to protect inode */
*/
if (dentry->d_lockref.count > 0) {
spin_unlock(&dentry->d_lock); spin_unlock(&dentry->d_lock);
if (parent) spin_lock(&inode->i_lock);
spin_unlock(&parent->d_lock); spin_lock(&dentry->d_lock);
continue; if (unlikely(dentry->d_lockref.count))
goto out;
/* changed inode means that somebody had grabbed it */
if (unlikely(inode != dentry->d_inode))
goto out;
rcu_read_unlock();
} }
parent = dentry->d_parent;
if (IS_ROOT(dentry) || likely(spin_trylock(&parent->d_lock)))
return true;
if (unlikely(dentry->d_flags & DCACHE_DENTRY_KILLED)) { rcu_read_lock(); /* to protect parent */
bool can_free = dentry->d_flags & DCACHE_MAY_FREE;
spin_unlock(&dentry->d_lock); spin_unlock(&dentry->d_lock);
if (parent) parent = READ_ONCE(dentry->d_parent);
spin_lock(&parent->d_lock);
if (unlikely(parent != dentry->d_parent)) {
spin_unlock(&parent->d_lock); spin_unlock(&parent->d_lock);
if (can_free) spin_lock(&dentry->d_lock);
dentry_free(dentry); goto out;
continue;
} }
spin_lock_nested(&dentry->d_lock, DENTRY_D_LOCK_NESTED);
if (likely(!dentry->d_lockref.count)) {
rcu_read_unlock();
return true;
}
spin_unlock(&parent->d_lock);
out:
if (inode)
spin_unlock(&inode->i_lock);
rcu_read_unlock();
return false;
}
inode = dentry->d_inode; static void shrink_dentry_list(struct list_head *list)
if (inode && unlikely(!spin_trylock(&inode->i_lock))) { {
d_shrink_add(dentry, list); while (!list_empty(list)) {
struct dentry *dentry, *parent;
struct inode *inode;
dentry = list_entry(list->prev, struct dentry, d_lru);
spin_lock(&dentry->d_lock);
if (!shrink_lock_dentry(dentry)) {
bool can_free = false;
d_shrink_del(dentry);
if (dentry->d_lockref.count < 0)
can_free = dentry->d_flags & DCACHE_MAY_FREE;
spin_unlock(&dentry->d_lock); spin_unlock(&dentry->d_lock);
if (parent) if (can_free)
spin_unlock(&parent->d_lock); dentry_free(dentry);
continue; continue;
} }
d_shrink_del(dentry);
parent = dentry->d_parent;
__dentry_kill(dentry); __dentry_kill(dentry);
if (parent == dentry)
continue;
/* /*
* We need to prune ancestors too. This is necessary to prevent * We need to prune ancestors too. This is necessary to prevent
* quadratic behavior of shrink_dcache_parent(), but is also * quadratic behavior of shrink_dcache_parent(), but is also
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment