ocfs2: avoid blocking in ocfs2_mark_lockres_freeing() in downconvert thread

If we are dropping last inode reference from downconvert thread, we will
end up calling ocfs2_mark_lockres_freeing() which can block if the lock
we are freeing is queued thus creating an A-A deadlock.  Luckily, since
we are the downconvert thread, we can immediately dequeue the lock and
thus avoid waiting in this case.

Signed-off-by: Jan Kara <jack@suse.cz>
Reviewed-by: Mark Fasheh <mfasheh@suse.de>
Reviewed-by: Srinivas Eeda <srinivas.eeda@oracle.com>
Cc: Joel Becker <jlbec@evilplan.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
Jan Kara
2014-04-03 14:46:57 -07:00
committed by Linus Torvalds
父節點 e3a767b60f
當前提交 84d86f83f9
共有 3 個文件被更改,包括 47 次插入7 次删除

查看文件

@@ -3144,22 +3144,60 @@ out:
return 0;
}
static void ocfs2_process_blocked_lock(struct ocfs2_super *osb,
struct ocfs2_lock_res *lockres);
/* Mark the lockres as being dropped. It will no longer be
* queued if blocking, but we still may have to wait on it
* being dequeued from the downconvert thread before we can consider
* it safe to drop.
*
* You can *not* attempt to call cluster_lock on this lockres anymore. */
void ocfs2_mark_lockres_freeing(struct ocfs2_lock_res *lockres)
void ocfs2_mark_lockres_freeing(struct ocfs2_super *osb,
struct ocfs2_lock_res *lockres)
{
int status;
struct ocfs2_mask_waiter mw;
unsigned long flags;
unsigned long flags, flags2;
ocfs2_init_mask_waiter(&mw);
spin_lock_irqsave(&lockres->l_lock, flags);
lockres->l_flags |= OCFS2_LOCK_FREEING;
if (lockres->l_flags & OCFS2_LOCK_QUEUED && current == osb->dc_task) {
/*
* We know the downconvert is queued but not in progress
* because we are the downconvert thread and processing
* different lock. So we can just remove the lock from the
* queue. This is not only an optimization but also a way
* to avoid the following deadlock:
* ocfs2_dentry_post_unlock()
* ocfs2_dentry_lock_put()
* ocfs2_drop_dentry_lock()
* iput()
* ocfs2_evict_inode()
* ocfs2_clear_inode()
* ocfs2_mark_lockres_freeing()
* ... blocks waiting for OCFS2_LOCK_QUEUED
* since we are the downconvert thread which
* should clear the flag.
*/
spin_unlock_irqrestore(&lockres->l_lock, flags);
spin_lock_irqsave(&osb->dc_task_lock, flags2);
list_del_init(&lockres->l_blocked_list);
osb->blocked_lock_count--;
spin_unlock_irqrestore(&osb->dc_task_lock, flags2);
/*
* Warn if we recurse into another post_unlock call. Strictly
* speaking it isn't a problem but we need to be careful if
* that happens (stack overflow, deadlocks, ...) so warn if
* ocfs2 grows a path for which this can happen.
*/
WARN_ON_ONCE(lockres->l_ops->post_unlock);
/* Since the lock is freeing we don't do much in the fn below */
ocfs2_process_blocked_lock(osb, lockres);
return;
}
while (lockres->l_flags & OCFS2_LOCK_QUEUED) {
lockres_add_mask_waiter(lockres, &mw, OCFS2_LOCK_QUEUED, 0);
spin_unlock_irqrestore(&lockres->l_lock, flags);
@@ -3180,7 +3218,7 @@ void ocfs2_simple_drop_lockres(struct ocfs2_super *osb,
{
int ret;
ocfs2_mark_lockres_freeing(lockres);
ocfs2_mark_lockres_freeing(osb, lockres);
ret = ocfs2_drop_lock(osb, lockres);
if (ret)
mlog_errno(ret);