diff options
author | Al Viro <viro@zeniv.linux.org.uk> | 2014-05-29 09:11:45 -0400 |
---|---|---|
committer | Jiri Slaby <jslaby@suse.cz> | 2014-10-30 22:17:45 +0100 |
commit | 641955231ecc5ddaeb2e6b917dd2e606359af6d3 (patch) | |
tree | 6a16e03a83fb67fe8b51d2394b25a1d4a67bb673 /fs | |
parent | f0d0762945b54220e4dc34672f8841918a81cd95 (diff) |
dealing with the rest of shrink_dentry_list() livelock
commit b2b80195d8829921506880f6dccd21cabd163d0d upstream.
We have the same problem with ->d_lock order in the inner loop, where
we are dropping references to ancestors. Same solution, basically -
instead of using dentry_kill() we use lock_parent() (introduced in the
previous commit) to get that lock in a safe way, recheck ->d_count
(in case if lock_parent() has ended up dropping and retaking ->d_lock
and somebody managed to grab a reference during that window), trylock
the inode->i_lock and use __dentry_kill() to do the rest.
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Jiri Slaby <jslaby@suse.cz>
Diffstat (limited to 'fs')
-rw-r--r-- | fs/dcache.c | 22 |
1 files changed, 20 insertions, 2 deletions
diff --git a/fs/dcache.c b/fs/dcache.c index f37f7b5ad18c..e58a23f93495 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -907,8 +907,26 @@ static void shrink_dentry_list(struct list_head *list) * fragmentation. */ dentry = parent; - while (dentry && !lockref_put_or_lock(&dentry->d_lockref)) - dentry = dentry_kill(dentry, 1); + while (dentry && !lockref_put_or_lock(&dentry->d_lockref)) { + parent = lock_parent(dentry); + if (dentry->d_lockref.count != 1) { + dentry->d_lockref.count--; + spin_unlock(&dentry->d_lock); + if (parent) + spin_unlock(&parent->d_lock); + break; + } + inode = dentry->d_inode; /* can't be NULL */ + if (unlikely(!spin_trylock(&inode->i_lock))) { + spin_unlock(&dentry->d_lock); + if (parent) + spin_unlock(&parent->d_lock); + cpu_relax(); + continue; + } + __dentry_kill(dentry); + dentry = parent; + } } } |