dcache: remove trylock loops (was Re: [BUG] lock_parent() breakage when used from shrink_dentry_list())

From: John Ogness
Date: Tue Feb 27 2018 - 00:16:42 EST


On 2018-02-25, Al Viro <viro@xxxxxxxxxxxxxxxxxx> wrote:
> I'll play with cleaning up and reordering tomorrow after I get some
> sleep. In the meanwhile, the current state of that set is at
> git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs.git #work.dcache

I have one comment on your new dentry_kill()...

> From e77db95d1ad189c76bcda7b90ac2225e98d776a2 Mon Sep 17 00:00:00 2001
> From: Al Viro <viro@xxxxxxxxxxxxxxxxxx>
> Date: Fri, 23 Feb 2018 21:25:42 -0500
> Subject: [PATCH] get rid of trylock loop around dentry_kill()
>
> In case when trylock in there fails, deal with it directly in
> dentry_kill(). Note that in cases when we drop and retake
> ->d_lock, we need to recheck whether to retain the dentry.
> Another thing is that dropping/retaking ->d_lock might have
> ended up with negative dentry turning into positive; that,
> of course, can happen only once...
>
> Signed-off-by: Al Viro <viro@xxxxxxxxxxxxxxxxxx>
> ---
> fs/dcache.c | 34 +++++++++++++++++++++++++++-------
> 1 file changed, 27 insertions(+), 7 deletions(-)
>
> diff --git a/fs/dcache.c b/fs/dcache.c
> index 449c1a5..c1f082d 100644
> --- a/fs/dcache.c
> +++ b/fs/dcache.c
> @@ -657,23 +657,43 @@ static struct dentry *dentry_kill(struct dentry *dentry)
> struct dentry *parent = NULL;
>
> if (inode && unlikely(!spin_trylock(&inode->i_lock)))
> - goto failed;
> + goto slow_positive;
>
> if (!IS_ROOT(dentry)) {
> parent = dentry->d_parent;
> if (unlikely(!spin_trylock(&parent->d_lock))) {
> - if (inode)
> - spin_unlock(&inode->i_lock);
> - goto failed;
> + parent = __lock_parent(dentry);
> + if (likely(inode || !dentry->d_inode))
> + goto got_locks;
> + /* negative that became positive */
> + if (parent)
> + spin_unlock(&parent->d_lock);
> + inode = dentry->d_inode;
> + goto slow_positive;
> }
> }
> -
> __dentry_kill(dentry);
> return parent;
>
> -failed:
> +slow_positive:
> + spin_unlock(&dentry->d_lock);
> + spin_lock(&inode->i_lock);
> + spin_lock(&dentry->d_lock);
> + parent = lock_parent(dentry);
> +got_locks:
> + if (likely(dentry->d_lockref.count == 1 && !retain_dentry(dentry))) {
> + __dentry_kill(dentry);
> + return parent;
> + }
> + /* we are keeping it, after all */
> + if (inode)
> + spin_unlock(&inode->i_lock);
> + if (parent)
> + spin_unlock(&parent->d_lock);

If someone else has grabbed a reference, it shouldn't be added to the
lru list. Only decremented.

if (entry->d_lockref.count == 1)

> + dentry_lru_add(dentry);
> + dentry->d_lockref.count--;
> spin_unlock(&dentry->d_lock);
> - return dentry; /* try again with same dentry */
> + return NULL;
> }
>
> /*