From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from zeniv.linux.org.uk ([195.92.253.2]:47296 "EHLO ZenIV.linux.org.uk" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751468AbeBXAWt (ORCPT ); Fri, 23 Feb 2018 19:22:49 -0500 Date: Sat, 24 Feb 2018 00:22:48 +0000 From: Al Viro To: Linus Torvalds Cc: linux-fsdevel , Christoph Hellwig , Thomas Gleixner , Peter Zijlstra , Sebastian Andrzej Siewior , Linux Kernel Mailing List , John Ogness Subject: Re: [BUG] lock_parent() breakage when used from shrink_dentry_list() (was Re: [PATCH v2 6/6] fs/dcache: Avoid remaining try_lock loop in shrink_dentry_list()) Message-ID: <20180224002248.GH30522@ZenIV.linux.org.uk> References: <20180222235025.28662-1-john.ogness@linutronix.de> <20180222235025.28662-7-john.ogness@linutronix.de> <20180223035814.GZ30522@ZenIV.linux.org.uk> <20180223040814.GA30522@ZenIV.linux.org.uk> <87h8q7erlo.fsf@linutronix.de> <20180223150928.GC30522@ZenIV.linux.org.uk> <20180223174216.GD30522@ZenIV.linux.org.uk> <20180223201317.GG30522@ZenIV.linux.org.uk> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: Sender: linux-fsdevel-owner@vger.kernel.org List-ID: On Fri, Feb 23, 2018 at 01:35:52PM -0800, Linus Torvalds wrote: > This is too subtle, and your fix to check d_lockref.count < 0 sounds > wrong to me. If it's really gone, maybe it has been reused and the > refcount is positive again, but it's something else than a dentry > entirely? > > Hmm. > > No, you extended the rcu read section, so I guess your patch is fine. > And lock_parent already has that pattern, soiit's not new. > > Ok, I agree, looks like lock_parent should just re-check that thing > that it already checked earler, but that now might be true again > because of we dropped d_lock. IMO that's the right thing for backports; whether we keep it after the getting rid of trylock loops is a different question. Note that the only case where we do not have __dentry_kill() prevention guaranteed by the caller (either by holding a reference, or by holding onto ->i_lock all along) is in shrink_dentry_list(). And there we have more than enough of other subtle crap. Moreover, there we have a good reason to treat "it had been moved" as "kick it off the shrink list and free if it's already dead", which might simplify the things. Below is a stab at that: /* * ONLY for shrink_dentry_list() - it returns false if it finds * dentry grabbed, moved or killed, which is fine there but not * anywhere else. OTOH, nobody else needs to deal with dentries * getting killed under them. */ static bool shrink_lock_for_kill(struct dentry *dentry) { if (dentry->d_lockref.count) return false; inode = dentry->d_inode; if (inode && unlikely(!spin_trylock(&inode->i_lock))) { rcu_read_lock(); /* to protect inode */ spin_unlock(&dentry->d_lock); spin_lock(&inode->i_lock); spin_lock(&dentry->d_lock); if (unlikely(dentry->d_lockref.count)) goto out; /* changed inode means that somebody had grabbed it */ if (unlikely(inode != dentry->d_inode)) goto out; rcu_read_unlock(); } parent = dentry->d_parent; if (IS_ROOT(dentry) || likely(spin_trylock(&parent->d_lock))) return true; rcu_read_lock(); /* to protect parent */ spin_unlock(&dentry->d_lock); parent = READ_ONCE(dentry->d_parent); spin_lock(&parent->d_lock); if (unlikely(parent != dentry->d_parent)) { spin_unlock(&parent->d_lock); spin_lock(&dentry->d_lock); goto out; } spin_lock_nested(&dentry->d_lock, DENTRY_D_LOCK_NESTED); if (likely(!dentry->d_lockref.count)) { rcu_read_unlock(); return true; } spin_unlock(&parent->d_lock); out: spin_unlock(&inode->i_lock); rcu_read_unlock(); return false; } static void shrink_dentry_list(struct list_head *list) { struct dentry *dentry, *parent; while (!list_empty(list)) { struct inode *inode; dentry = list_entry(list->prev, struct dentry, d_lru); spin_lock(&dentry->d_lock); if (!shrink_lock_for_kill(dentry)) { bool can_free = false; d_shrink_del(dentry); if (dentry->d_lockref.count < 0) can_free = dentry->d_flags & DCACHE_MAY_FREE; spin_unlock(&dentry->d_lock); if (can_free) dentry_free(dentry); continue; } d_shrink_del(dentry); parent = dentry->d_parent; __dentry_kill(dentry); if (dentry == parent) continue; dentry = parent; .... same as now .... } }