mirror of
https://github.com/Fishwaldo/Star64_linux.git
synced 2025-04-20 05:13:59 +00:00
btrfs: qgroup: don't try to wait flushing if we're already holding a 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: stable@vger.kernel.org # 5.9+ Reviewed-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: Qu Wenruo <wqu@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
This commit is contained in:
parent
9a66497156
commit
ae5e070eac
1 changed files with 20 additions and 10 deletions
|
@ -3530,16 +3530,6 @@ static int try_flush_qgroup(struct btrfs_root *root)
|
||||||
int ret;
|
int ret;
|
||||||
bool can_commit = true;
|
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
|
* If current process holds a transaction, we shouldn't flush, as we
|
||||||
* assume all space reservation happens before a transaction handle is
|
* assume all space reservation happens before a transaction handle is
|
||||||
|
@ -3554,6 +3544,26 @@ static int try_flush_qgroup(struct btrfs_root *root)
|
||||||
current->journal_info != BTRFS_SEND_TRANS_STUB)
|
current->journal_info != BTRFS_SEND_TRANS_STUB)
|
||||||
can_commit = false;
|
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);
|
ret = btrfs_start_delalloc_snapshot(root);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
Loading…
Add table
Reference in a new issue