[PATCH 2/3] dcache: wait for a racing eviction of a secondary root
From: Christian Brauner
Date: Fri Oct 30 2026 - 08:39:41 EST
From: Josef Bacik <josef@xxxxxxxxxxxxxx>
Commit e9895609cb7f ("wind ->s_roots via ->d_sib instead of ->d_hash")
keeps a secondary root on ->s_roots until dentry_unlist() so that
shrink_dcache_for_umount() can find one that another thread is evicting
and wait for it. But do_one_tree() takes every root off the list before
its dput() which breaks that:
thread A umount
dentry_kill(root), refcount is 0
lock_for_kill()
spin_trylock(->i_lock) fails
spin_unlock(->d_lock)
shrink_dcache_for_umount()
dget_dlock(root)
do_one_tree(root)
unlink_secondary_root(root)
spin_lock(->i_lock)
dput(root), refcount is 0
lock_for_kill()
trylock fails, waits
spin_lock(->d_lock)
refcount is 0 again, go on
lockref_mark_dead()
dentry_unlink_inode()
drops both locks
root is dead, give up
->s_roots is empty, done
evict_inodes()
"Busy inodes after unmount"
iput()
Thread A evicts without pinning the superblock. namespace_unlock() does
that for old mountpoints after dropping namespace_sem, and a secondary
root goes with its last child.
So only take busy roots off the list. Leave the others to
dentry_unlist() so that shrink_dcache_for_umount() finds them and waits
for an eviction in progress. A root that becomes busy after the check is
picked up again and taken off the list then.
Fixes: 78db93943210 ("dcache: keep shrink_dcache_for_umount() making progress on busy roots")
Cc: stable@xxxxxxxxxxxxxxx # v7.2+
Signed-off-by: Josef Bacik <josef@xxxxxxxxxxxxxx>
Signed-off-by: Christian Brauner (Amutable) <brauner@xxxxxxxxxx>
---
fs/dcache.c | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/fs/dcache.c b/fs/dcache.c
index 72b8e0adab0d..c1d8b398b2d1 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -1817,8 +1817,13 @@ static void do_one_tree(struct dentry *dentry)
d_walk(dentry, dentry, umount_check);
spin_lock(&dentry->d_lock);
__d_drop(dentry);
- /* A busy root survives the dput() below so don't leave it on ->s_roots. */
- if (unlikely(!hlist_unhashed(&dentry->d_sib)))
+ /*
+ * A busy root survives the dput() below so don't leave it on ->s_roots.
+ * An idle one stays until dentry_unlist() so that umount can wait for
+ * a racing eviction, see lock_for_kill().
+ */
+ if (unlikely(dentry->d_lockref.count > 1 &&
+ !hlist_unhashed(&dentry->d_sib)))
unlink_secondary_root(dentry);
spin_unlock(&dentry->d_lock);
dput(dentry);
--
2.53.0