Skip to content

Commit

Permalink
btrfs: qgroup: don't try to wait flushing if we're already holding a …
Browse files Browse the repository at this point in the history
…transaction

There is a chance of racing for qgroup flushing which may lead to
deadlock:

	Thread A		|	Thread B
   (not holding trans handle)	|  (holding a trans handle)
--------------------------------+--------------------------------
__btrfs_qgroup_reserve_meta()   | __btrfs_qgroup_reserve_meta()
|- try_flush_qgroup()		| |- try_flush_qgroup()
   |- QGROUP_FLUSHING bit set   |    |
   |				|    |- test_and_set_bit()
   |				|    |- wait_event()
   |- btrfs_join_transaction()	|
   |- btrfs_commit_transaction()|

			!!! DEAD LOCK !!!

Since thread A wants to commit transaction, but thread B is holding a
transaction handle, blocking the commit.
At the same time, thread B is waiting for thread A to finish its commit.

This is just a hot fix, and would lead to more EDQUOT when we're near
the qgroup limit.

The proper fix would be to make all metadata/data reservations happen
without holding a transaction handle.

CC: [email protected] # 5.9+
Reviewed-by: Filipe Manana <[email protected]>
Signed-off-by: Qu Wenruo <[email protected]>
Signed-off-by: David Sterba <[email protected]>
  • Loading branch information
adam900710 authored and kdave committed Dec 18, 2020
1 parent 9a66497 commit ae5e070
Showing 1 changed file with 20 additions and 10 deletions.
30 changes: 20 additions & 10 deletions fs/btrfs/qgroup.c
Original file line number Diff line number Diff line change
Expand Up @@ -3530,16 +3530,6 @@ static int try_flush_qgroup(struct btrfs_root *root)
int ret;
bool can_commit = true;

/*
* We don't want to run flush again and again, so if there is a running
* one, we won't try to start a new flush, but exit directly.
*/
if (test_and_set_bit(BTRFS_ROOT_QGROUP_FLUSHING, &root->state)) {
wait_event(root->qgroup_flush_wait,
!test_bit(BTRFS_ROOT_QGROUP_FLUSHING, &root->state));
return 0;
}

/*
* If current process holds a transaction, we shouldn't flush, as we
* assume all space reservation happens before a transaction handle is
Expand All @@ -3554,6 +3544,26 @@ static int try_flush_qgroup(struct btrfs_root *root)
current->journal_info != BTRFS_SEND_TRANS_STUB)
can_commit = false;

/*
* We don't want to run flush again and again, so if there is a running
* one, we won't try to start a new flush, but exit directly.
*/
if (test_and_set_bit(BTRFS_ROOT_QGROUP_FLUSHING, &root->state)) {
/*
* We are already holding a transaction, thus we can block other
* threads from flushing. So exit right now. This increases
* the chance of EDQUOT for heavy load and near limit cases.
* But we can argue that if we're already near limit, EDQUOT is
* unavoidable anyway.
*/
if (!can_commit)
return 0;

wait_event(root->qgroup_flush_wait,
!test_bit(BTRFS_ROOT_QGROUP_FLUSHING, &root->state));
return 0;
}

ret = btrfs_start_delalloc_snapshot(root);
if (ret < 0)
goto out;
Expand Down

0 comments on commit ae5e070

Please sign in to comment.