Skip to content
  • Al Viro's avatar
    shrink_dentry_list(): take parent's ->d_lock earlier · 046b961b
    Al Viro authored
    
    
    The cause of livelocks there is that we are taking ->d_lock on
    dentry and its parent in the wrong order, forcing us to use
    trylock on the parent's one.  d_walk() takes them in the right
    order, and unfortunately it's not hard to create a situation
    when shrink_dentry_list() can't make progress since trylock
    keeps failing, and shrink_dcache_parent() or check_submounts_and_drop()
    keeps calling d_walk() disrupting the very shrink_dentry_list() it's
    waiting for.
    
    Solution is straightforward - if that trylock fails, let's unlock
    the dentry itself and take locks in the right order.  We need to
    stabilize ->d_parent without holding ->d_lock, but that's doable
    using RCU.  And we'd better do that in the very beginning of the
    loop in shrink_dentry_list(), since the checks on refcount, etc.
    would need to be redone anyway.
    
    That deals with a half of the problem - killing dentries on the
    shrink list itself.  Another one (dropping their parents) is
    in the next commit.
    
    locking parent is interesting - it would be easy to do rcu_read_lock(),
    lock whatever we think is a parent, lock dentry itself and check
    if the parent is still the right one.  Except that we need to check
    that *before* locking the dentry, or we are risking taking ->d_lock
    out of order.  Fortunately, once the D1 is locked, we can check if
    D2->d_parent is equal to D1 without the need to lock D2; D2->d_parent
    can start or stop pointing to D1 only under D1->d_lock, so taking
    D1->d_lock is enough.  In other words, the right solution is
    rcu_read_lock/lock what looks like parent right now/check if it's
    still our parent/rcu_read_unlock/lock the child.
    
    Signed-off-by: default avatarAl Viro <viro@zeniv.linux.org.uk>
    046b961b