ocfs2: implement delayed dropping of last dquot reference

We cannot drop last dquot reference from downconvert thread as that
creates the following deadlock:

NODE 1                                  NODE2
holds dentry lock for 'foo'
holds inode lock for GLOBAL_BITMAP_SYSTEM_INODE
                                        dquot_initialize(bar)
                                          ocfs2_dquot_acquire()
                                            ocfs2_inode_lock(USER_QUOTA_SYSTEM_INODE)
                                            ...
downconvert thread (triggered from another
node or a different process from NODE2)
  ocfs2_dentry_post_unlock()
    ...
    iput(foo)
      ocfs2_evict_inode(foo)
        ocfs2_clear_inode(foo)
          dquot_drop(inode)
            ...
	    ocfs2_dquot_release()
              ocfs2_inode_lock(USER_QUOTA_SYSTEM_INODE)
               - blocks
                                            finds we need more space in
                                            quota file
                                            ...
                                            ocfs2_extend_no_holes()
                                              ocfs2_inode_lock(GLOBAL_BITMAP_SYSTEM_INODE)
                                                - deadlocks waiting for
                                                  downconvert thread

We solve the problem by postponing dropping of the last dquot reference to
a workqueue if it happens from the downconvert thread.

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:56 -07:00 committed by Linus Torvalds
parent 9f985cb6c4
commit e3a767b60f
4 changed files with 50 additions and 0 deletions

View File

@ -30,6 +30,7 @@
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/list.h>
#include <linux/llist.h>
#include <linux/rbtree.h>
#include <linux/workqueue.h>
#include <linux/kref.h>
@ -419,6 +420,10 @@ struct ocfs2_super
struct ocfs2_dentry_lock *dentry_lock_list;
struct work_struct dentry_lock_work;
/* List of dquot structures to drop last reference to */
struct llist_head dquot_drop_list;
struct work_struct dquot_drop_work;
wait_queue_head_t osb_mount_event;
/* Truncate log info */

View File

@ -28,6 +28,7 @@ struct ocfs2_dquot {
unsigned int dq_use_count; /* Number of nodes having reference to this entry in global quota file */
s64 dq_origspace; /* Last globally synced space usage */
s64 dq_originodes; /* Last globally synced inode usage */
struct llist_node list; /* Member of list of dquots to drop */
};
/* Description of one chunk to recover in memory */
@ -110,6 +111,7 @@ int ocfs2_read_quota_phys_block(struct inode *inode, u64 p_block,
int ocfs2_create_local_dquot(struct dquot *dquot);
int ocfs2_local_release_dquot(handle_t *handle, struct dquot *dquot);
int ocfs2_local_write_dquot(struct dquot *dquot);
void ocfs2_drop_dquot_refs(struct work_struct *work);
extern const struct dquot_operations ocfs2_quota_operations;
extern struct quota_format_type ocfs2_quota_format;

View File

@ -10,6 +10,7 @@
#include <linux/jiffies.h>
#include <linux/writeback.h>
#include <linux/workqueue.h>
#include <linux/llist.h>
#include <cluster/masklog.h>
@ -679,6 +680,27 @@ static int ocfs2_calc_qdel_credits(struct super_block *sb, int type)
OCFS2_INODE_UPDATE_CREDITS;
}
void ocfs2_drop_dquot_refs(struct work_struct *work)
{
struct ocfs2_super *osb = container_of(work, struct ocfs2_super,
dquot_drop_work);
struct llist_node *list;
struct ocfs2_dquot *odquot, *next_odquot;
list = llist_del_all(&osb->dquot_drop_list);
llist_for_each_entry_safe(odquot, next_odquot, list, list) {
/* Drop the reference we acquired in ocfs2_dquot_release() */
dqput(&odquot->dq_dquot);
}
}
/*
* Called when the last reference to dquot is dropped. If we are called from
* downconvert thread, we cannot do all the handling here because grabbing
* quota lock could deadlock (the node holding the quota lock could need some
* other cluster lock to proceed but with blocked downconvert thread we cannot
* release any lock).
*/
static int ocfs2_release_dquot(struct dquot *dquot)
{
handle_t *handle;
@ -694,6 +716,19 @@ static int ocfs2_release_dquot(struct dquot *dquot)
/* Check whether we are not racing with some other dqget() */
if (atomic_read(&dquot->dq_count) > 1)
goto out;
/* Running from downconvert thread? Postpone quota processing to wq */
if (current == osb->dc_task) {
/*
* Grab our own reference to dquot and queue it for delayed
* dropping. Quota code rechecks after calling
* ->release_dquot() and won't free dquot structure.
*/
dqgrab(dquot);
/* First entry on list -> queue work */
if (llist_add(&OCFS2_DQUOT(dquot)->list, &osb->dquot_drop_list))
queue_work(ocfs2_wq, &osb->dquot_drop_work);
goto out;
}
status = ocfs2_lock_global_qf(oinfo, 1);
if (status < 0)
goto out;

View File

@ -1941,6 +1941,11 @@ static void ocfs2_dismount_volume(struct super_block *sb, int mnt_err)
ocfs2_disable_quotas(osb);
/* All dquots should be freed by now */
WARN_ON(!llist_empty(&osb->dquot_drop_list));
/* Wait for worker to be done with the work structure in osb */
cancel_work_sync(&osb->dquot_drop_work);
ocfs2_shutdown_local_alloc(osb);
/* This will disable recovery and flush any recovery work. */
@ -2276,6 +2281,9 @@ static int ocfs2_initialize_super(struct super_block *sb,
INIT_WORK(&osb->dentry_lock_work, ocfs2_drop_dl_inodes);
osb->dentry_lock_list = NULL;
INIT_WORK(&osb->dquot_drop_work, ocfs2_drop_dquot_refs);
init_llist_head(&osb->dquot_drop_list);
/* get some pseudo constants for clustersize bits */
osb->s_clustersize_bits =
le32_to_cpu(di->id2.i_super.s_clustersize_bits);