Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3b3f09f4 authored by Al Viro's avatar Al Viro
Browse files

get rid of trylock loop in locking dentries on shrink list



In case of trylock failure don't re-add to the list - drop the locks
and carefully get them in the right order.  For shrink_dentry_list(),
somebody having grabbed a reference to dentry means that we can
kick it off-list, so if we find dentry being modified under us we
don't need to play silly buggers with retries anyway - off the list
it is.

The locking logics taken out into a helper of its own; lock_parent()
is no longer used for dentries that can be killed under us.

[fix from Eric Biggers folded]

Signed-off-by: default avatarAl Viro <viro@zeniv.linux.org.uk>
parent c19457f0
Loading
Loading
Loading
Loading
+67 −37
Original line number Diff line number Diff line
@@ -974,56 +974,86 @@ void d_prune_aliases(struct inode *inode)
}
EXPORT_SYMBOL(d_prune_aliases);

static void shrink_dentry_list(struct list_head *list)
/*
 * Lock a dentry from shrink list.
 * Note that dentry is *not* protected from concurrent dentry_kill(),
 * d_delete(), etc.  It is protected from freeing (by the fact of
 * being on a shrink list), but everything else is fair game.
 * Return false if dentry has been disrupted or grabbed, leaving
 * the caller to kick it off-list.  Otherwise, return true and have
 * that dentry's inode and parent both locked.
 */
static bool shrink_lock_dentry(struct dentry *dentry)
{
	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);
		parent = lock_parent(dentry);
	struct dentry *parent;

		/*
		 * The dispose list is isolated and dentries are not accounted
		 * to the LRU here, so we can simply remove it from the list
		 * here regardless of whether it is referenced or not.
		 */
		d_shrink_del(dentry);
	if (dentry->d_lockref.count)
		return false;

		/*
		 * We found an inuse dentry which was not removed from
		 * the LRU because of laziness during lookup. Do not free it.
		 */
		if (dentry->d_lockref.count > 0) {
	inode = dentry->d_inode;
	if (inode && unlikely(!spin_trylock(&inode->i_lock))) {
		rcu_read_lock();	/* to protect inode */
		spin_unlock(&dentry->d_lock);
			if (parent)
				spin_unlock(&parent->d_lock);
			continue;
		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;

		if (unlikely(dentry->d_flags & DCACHE_DENTRY_KILLED)) {
			bool can_free = dentry->d_flags & DCACHE_MAY_FREE;
	rcu_read_lock();		/* to protect parent */
	spin_unlock(&dentry->d_lock);
			if (parent)
	parent = READ_ONCE(dentry->d_parent);
	spin_lock(&parent->d_lock);
	if (unlikely(parent != dentry->d_parent)) {
		spin_unlock(&parent->d_lock);
			if (can_free)
				dentry_free(dentry);
			continue;
		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:
	if (inode)
		spin_unlock(&inode->i_lock);
	rcu_read_unlock();
	return false;
}

		inode = dentry->d_inode;
		if (inode && unlikely(!spin_trylock(&inode->i_lock))) {
			d_shrink_add(dentry, list);
static void shrink_dentry_list(struct list_head *list)
{
	while (!list_empty(list)) {
		struct dentry *dentry, *parent;
		struct inode *inode;

		dentry = list_entry(list->prev, struct dentry, d_lru);
		spin_lock(&dentry->d_lock);
		if (!shrink_lock_dentry(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 (parent)
				spin_unlock(&parent->d_lock);
			if (can_free)
				dentry_free(dentry);
			continue;
		}

		d_shrink_del(dentry);
		parent = dentry->d_parent;
		__dentry_kill(dentry);

		if (parent == dentry)
			continue;
		/*
		 * We need to prune ancestors too. This is necessary to prevent
		 * quadratic behavior of shrink_dcache_parent(), but is also