mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-05 02:21:52 +09:00
btrfs: make btrfs_discard_workfn() block_group ref explicit
[ Upstream commit 895c6721d310c036dcfebb5ab845822229fa35eb ] Currently, the async discard machinery owns a ref to the block_group when the block_group is queued on a discard list. However, to handle races with discard cancellation and the discard workfn, we have a specific logic to detect that the block_group is *currently* running in the workfn, to protect the workfn's usage amidst cancellation. As far as I can tell, this doesn't have any overt bugs (though finish_discard_pass() and remove_from_discard_list() racing can have a surprising outcome for the caller of remove_from_discard_list() in that it is again added at the end). But it is needlessly complicated to rely on locking and the nullity of discard_ctl->block_group. Simplify this significantly by just taking a refcount while we are in the workfn and unconditionally drop it in both the remove and workfn paths, regardless of if they race. Reviewed-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: Boris Burkov <boris@bur.io> Signed-off-by: David Sterba <dsterba@suse.com> Signed-off-by: Sasha Levin <sashal@kernel.org>
This commit is contained in:
committed by
Greg Kroah-Hartman
parent
e212c8e9f2
commit
df4af023f6
@@ -152,13 +152,7 @@ static bool remove_from_discard_list(struct btrfs_discard_ctl *discard_ctl,
|
|||||||
block_group->discard_eligible_time = 0;
|
block_group->discard_eligible_time = 0;
|
||||||
queued = !list_empty(&block_group->discard_list);
|
queued = !list_empty(&block_group->discard_list);
|
||||||
list_del_init(&block_group->discard_list);
|
list_del_init(&block_group->discard_list);
|
||||||
/*
|
if (queued)
|
||||||
* If the block group is currently running in the discard workfn, we
|
|
||||||
* don't want to deref it, since it's still being used by the workfn.
|
|
||||||
* The workfn will notice this case and deref the block group when it is
|
|
||||||
* finished.
|
|
||||||
*/
|
|
||||||
if (queued && !running)
|
|
||||||
btrfs_put_block_group(block_group);
|
btrfs_put_block_group(block_group);
|
||||||
|
|
||||||
spin_unlock(&discard_ctl->lock);
|
spin_unlock(&discard_ctl->lock);
|
||||||
@@ -256,9 +250,10 @@ again:
|
|||||||
block_group->discard_cursor = block_group->start;
|
block_group->discard_cursor = block_group->start;
|
||||||
block_group->discard_state = BTRFS_DISCARD_EXTENTS;
|
block_group->discard_state = BTRFS_DISCARD_EXTENTS;
|
||||||
}
|
}
|
||||||
discard_ctl->block_group = block_group;
|
|
||||||
}
|
}
|
||||||
if (block_group) {
|
if (block_group) {
|
||||||
|
btrfs_get_block_group(block_group);
|
||||||
|
discard_ctl->block_group = block_group;
|
||||||
*discard_state = block_group->discard_state;
|
*discard_state = block_group->discard_state;
|
||||||
*discard_index = block_group->discard_index;
|
*discard_index = block_group->discard_index;
|
||||||
}
|
}
|
||||||
@@ -482,9 +477,20 @@ static void btrfs_discard_workfn(struct work_struct *work)
|
|||||||
|
|
||||||
block_group = peek_discard_list(discard_ctl, &discard_state,
|
block_group = peek_discard_list(discard_ctl, &discard_state,
|
||||||
&discard_index, now);
|
&discard_index, now);
|
||||||
if (!block_group || !btrfs_run_discard_work(discard_ctl))
|
if (!block_group)
|
||||||
return;
|
return;
|
||||||
|
if (!btrfs_run_discard_work(discard_ctl)) {
|
||||||
|
spin_lock(&discard_ctl->lock);
|
||||||
|
btrfs_put_block_group(block_group);
|
||||||
|
discard_ctl->block_group = NULL;
|
||||||
|
spin_unlock(&discard_ctl->lock);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (now < block_group->discard_eligible_time) {
|
if (now < block_group->discard_eligible_time) {
|
||||||
|
spin_lock(&discard_ctl->lock);
|
||||||
|
btrfs_put_block_group(block_group);
|
||||||
|
discard_ctl->block_group = NULL;
|
||||||
|
spin_unlock(&discard_ctl->lock);
|
||||||
btrfs_discard_schedule_work(discard_ctl, false);
|
btrfs_discard_schedule_work(discard_ctl, false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -536,15 +542,7 @@ static void btrfs_discard_workfn(struct work_struct *work)
|
|||||||
spin_lock(&discard_ctl->lock);
|
spin_lock(&discard_ctl->lock);
|
||||||
discard_ctl->prev_discard = trimmed;
|
discard_ctl->prev_discard = trimmed;
|
||||||
discard_ctl->prev_discard_time = now;
|
discard_ctl->prev_discard_time = now;
|
||||||
/*
|
btrfs_put_block_group(block_group);
|
||||||
* If the block group was removed from the discard list while it was
|
|
||||||
* running in this workfn, then we didn't deref it, since this function
|
|
||||||
* still owned that reference. But we set the discard_ctl->block_group
|
|
||||||
* back to NULL, so we can use that condition to know that now we need
|
|
||||||
* to deref the block_group.
|
|
||||||
*/
|
|
||||||
if (discard_ctl->block_group == NULL)
|
|
||||||
btrfs_put_block_group(block_group);
|
|
||||||
discard_ctl->block_group = NULL;
|
discard_ctl->block_group = NULL;
|
||||||
__btrfs_discard_schedule_work(discard_ctl, now, false);
|
__btrfs_discard_schedule_work(discard_ctl, now, false);
|
||||||
spin_unlock(&discard_ctl->lock);
|
spin_unlock(&discard_ctl->lock);
|
||||||
|
|||||||
Reference in New Issue
Block a user