Commit 91182645 authored by Josef Bacik's avatar Josef Bacik Committed by David Sterba

btrfs: stop partially refilling tickets when releasing space

btrfs_space_info_add_old_bytes is used when adding the extra space from
an existing reservation back into the space_info to be used by any
waiting tickets.  In order to keep us from overcommitting we check to
make sure that we can still use this space for our reserve ticket, and
if we cannot we'll simply subtract it from space_info->bytes_may_use.

However this is problematic, because it assumes that only changes to
bytes_may_use would affect our ability to make reservations.  Any
changes to bytes_reserved would be missed.  If we were unable to make a
reservation prior because of reserved space, but that reserved space was
free'd due to unlink or truncate and we were allowed to immediately
reclaim that metadata space we would still ENOSPC.

Consider the example where we create a file with a bunch of extents,
using up 2MiB of actual space for the new tree blocks.  Then we try to
make a reservation of 2MiB but we do not have enough space to make this
reservation.  The iput() occurs in another thread and we remove this
space, and since we did not write the blocks we simply do
space_info->bytes_reserved -= 2MiB.  We would never see this because we
do not check our space info used, we just try to re-use the freed
reservations.

To fix this problem, and to greatly simplify the wakeup code, do away
with this partial refilling nonsense.  Use
btrfs_space_info_add_old_bytes to subtract the reservation from
space_info->bytes_may_use, and then check the ticket against the total
used of the space_info the same way we do with the initial reservation
attempt.

This keeps the reservation logic consistent and solves the problem of
early ENOSPC in the case that we free up space in places other than
bytes_may_use and bytes_pinned.  Thanks,
Signed-off-by: default avatarJosef Bacik <josef@toxicpanda.com>
Signed-off-by: default avatarDavid Sterba <dsterba@suse.com>
parent a43c3835
...@@ -233,52 +233,41 @@ void btrfs_space_info_add_old_bytes(struct btrfs_fs_info *fs_info, ...@@ -233,52 +233,41 @@ void btrfs_space_info_add_old_bytes(struct btrfs_fs_info *fs_info,
struct btrfs_space_info *space_info, struct btrfs_space_info *space_info,
u64 num_bytes) u64 num_bytes)
{ {
struct reserve_ticket *ticket;
struct list_head *head; struct list_head *head;
u64 used;
enum btrfs_reserve_flush_enum flush = BTRFS_RESERVE_NO_FLUSH; enum btrfs_reserve_flush_enum flush = BTRFS_RESERVE_NO_FLUSH;
bool check_overcommit = false;
spin_lock(&space_info->lock); spin_lock(&space_info->lock);
head = &space_info->priority_tickets; head = &space_info->priority_tickets;
btrfs_space_info_update_bytes_may_use(fs_info, space_info, -num_bytes);
/*
* If we are over our limit then we need to check and see if we can
* overcommit, and if we can't then we just need to free up our space
* and not satisfy any requests.
*/
used = btrfs_space_info_used(space_info, true);
if (used - num_bytes >= space_info->total_bytes)
check_overcommit = true;
again: again:
while (!list_empty(head) && num_bytes) { while (!list_empty(head)) {
ticket = list_first_entry(head, struct reserve_ticket, struct reserve_ticket *ticket;
list); u64 used = btrfs_space_info_used(space_info, true);
/*
* We use 0 bytes because this space is already reserved, so ticket = list_first_entry(head, struct reserve_ticket, list);
* adding the ticket space would be a double count.
*/ /* Check and see if our ticket can be satisified now. */
if (check_overcommit && if ((used + ticket->bytes <= space_info->total_bytes) ||
!can_overcommit(fs_info, space_info, 0, flush, false)) can_overcommit(fs_info, space_info, ticket->bytes, flush,
break; false)) {
if (num_bytes >= ticket->bytes) { btrfs_space_info_update_bytes_may_use(fs_info,
space_info,
ticket->bytes);
list_del_init(&ticket->list); list_del_init(&ticket->list);
num_bytes -= ticket->bytes;
ticket->bytes = 0; ticket->bytes = 0;
space_info->tickets_id++; space_info->tickets_id++;
wake_up(&ticket->wait); wake_up(&ticket->wait);
} else { } else {
ticket->bytes -= num_bytes; break;
num_bytes = 0;
} }
} }
if (num_bytes && head == &space_info->priority_tickets) { if (head == &space_info->priority_tickets) {
head = &space_info->tickets; head = &space_info->tickets;
flush = BTRFS_RESERVE_FLUSH_ALL; flush = BTRFS_RESERVE_FLUSH_ALL;
goto again; goto again;
} }
btrfs_space_info_update_bytes_may_use(fs_info, space_info, -num_bytes);
spin_unlock(&space_info->lock); spin_unlock(&space_info->lock);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment