Re: shrink_dentry_list() logics change (was Re: [RFC PATCH v3 14/15] dcache: Implement partial shrink via Slab Movable Objects)

From: Tobin C. Harding
Date: Mon Jul 01 2019 - 05:26:36 EST


On Sat, Jun 29, 2019 at 08:06:24PM +0100, Al Viro wrote:
> On Sat, Jun 29, 2019 at 05:38:03AM +0100, Al Viro wrote:
>
> > PS: the problem is not gone in the next iteration of the patchset in
> > question. The patch I'm proposing (including dput_to_list() and _ONLY_
> > compile-tested) follows. Comments?
>
> FWIW, there's another unpleasantness in the whole thing. Suppose we have
> picked a page full of dentries, all with refcount 0. We decide to
> evict all of them. As it turns out, they are from two filesystems.
> Filesystem 1 is NFS on a server, with currently downed hub on the way
> to it. Filesystem 2 is local. We attempt to evict an NFS dentry and
> get stuck - tons of dirty data with no way to flush them on server.
> In the meanwhile, admin tries to unmount the local filesystem. And
> gets stuck as well, since umount can't do anything to its dentries
> that happen to sit in our shrink list.
>
> I wonder if the root of problem here isn't in shrink_dcache_for_umount();
> all it really needs is to have everything on that fs with refcount 0
> dragged through __dentry_kill(). If something had been on a shrink
> list, __dentry_kill() will just leave behind a struct dentry completely
> devoid of any connection to superblock, other dentries, filesystem
> type, etc. - it's just a piece of memory that won't be freed until
> the owner of shrink list finally gets around to it. Which can happen
> at any point - all they'll do to it is dentry_free(), and that doesn't
> need any fs-related data structures.
>
> The logics in shrink_dcache_parent() is
> collect everything evictable into a shrink list
> if anything found - kick it out and repeat the scan
> otherwise, if something had been on other's shrink list
> repeat the scan
>
> I wonder if after the "no evictable candidates, but something
> on other's shrink lists" we ought to do something along the
> lines of
> rcu_read_lock
> walk it, doing
> if dentry has zero refcount
> if it's not on a shrink list,
> move it to ours
> else
> store its address in 'victim'
> end the walk
> if no victim found
> rcu_read_unlock
> else
> lock victim for __dentry_kill
> rcu_read_unlock
> if it's still alive
> if it's not IS_ROOT
> if parent is not on shrink list
> decrement parent's refcount
> put it on our list
> else
> decrement parent's refcount
> __dentry_kill(victim)
> else
> unlock
> if our list is non-empty
> shrink_dentry_list on it
> in there...

Thanks for still thinking about this Al. I don't have a lot of idea
about what to do with your comments until I can grok them fully but I
wanted to acknowledge having read them.

Thanks,
Tobin.