Commit ba117213 authored by Josef Bacik's avatar Josef Bacik

Btrfs: account merges/splits properly

My fix

Btrfs: fix merge delalloc logic

only fixed half of the problems, it didn't fix the case where we have two large
extents on either side and then join them together with a new small extent.  We
need to instead keep track of how many extents we have accounted for with each
side of the new extent, and then see how many extents we need for the new large
extent.  If they match then we know we need to keep our reservation, otherwise
we need to drop our reservation.  This shows up with a case like this

[BTRFS_MAX_EXTENT_SIZE+4K][4K HOLE][BTRFS_MAX_EXTENT_SIZE+4K]

Previously the logic would have said that the number extents required for the
new size (3) is larger than the number of extents required for the largest side
(2) therefore we need to keep our reservation.  But this isn't the case, since
both sides require a reservation of 2 which leads to 4 for the whole range
currently reserved, but we only need 3, so we need to drop one of the
reservations.  The same problem existed for splits, we'd think we only need 3
extents when creating the hole but in reality we need 4.  Thanks,
Signed-off-by: default avatarJosef Bacik <jbacik@fb.com>
parent dcdf7f6d
......@@ -1542,30 +1542,17 @@ static void btrfs_split_extent_hook(struct inode *inode,
u64 new_size;
/*
* We need the largest size of the remaining extent to see if we
* need to add a new outstanding extent. Think of the following
* case
*
* [MEAX_EXTENT_SIZEx2 - 4k][4k]
*
* The new_size would just be 4k and we'd think we had enough
* outstanding extents for this if we only took one side of the
* split, same goes for the other direction. We need to see if
* the larger size still is the same amount of extents as the
* original size, because if it is we need to add a new
* outstanding extent. But if we split up and the larger size
* is less than the original then we are good to go since we've
* already accounted for the extra extent in our original
* accounting.
* See the explanation in btrfs_merge_extent_hook, the same
* applies here, just in reverse.
*/
new_size = orig->end - split + 1;
if ((split - orig->start) > new_size)
num_extents = div64_u64(new_size + BTRFS_MAX_EXTENT_SIZE - 1,
BTRFS_MAX_EXTENT_SIZE);
new_size = split - orig->start;
num_extents = div64_u64(size + BTRFS_MAX_EXTENT_SIZE - 1,
num_extents += div64_u64(new_size + BTRFS_MAX_EXTENT_SIZE - 1,
BTRFS_MAX_EXTENT_SIZE);
if (div64_u64(new_size + BTRFS_MAX_EXTENT_SIZE - 1,
BTRFS_MAX_EXTENT_SIZE) < num_extents)
if (div64_u64(size + BTRFS_MAX_EXTENT_SIZE - 1,
BTRFS_MAX_EXTENT_SIZE) >= num_extents)
return;
}
......@@ -1591,9 +1578,6 @@ static void btrfs_merge_extent_hook(struct inode *inode,
if (!(other->state & EXTENT_DELALLOC))
return;
old_size = other->end - other->start + 1;
if (old_size < (new->end - new->start + 1))
old_size = (new->end - new->start + 1);
if (new->start > other->start)
new_size = new->end - other->start + 1;
else
......@@ -1608,13 +1592,32 @@ static void btrfs_merge_extent_hook(struct inode *inode,
}
/*
* If we grew by another max_extent, just return, we want to keep that
* reserved amount.
* We have to add up either side to figure out how many extents were
* accounted for before we merged into one big extent. If the number of
* extents we accounted for is <= the amount we need for the new range
* then we can return, otherwise drop. Think of it like this
*
* [ 4k][MAX_SIZE]
*
* So we've grown the extent by a MAX_SIZE extent, this would mean we
* need 2 outstanding extents, on one side we have 1 and the other side
* we have 1 so they are == and we can return. But in this case
*
* [MAX_SIZE+4k][MAX_SIZE+4k]
*
* Each range on their own accounts for 2 extents, but merged together
* they are only 3 extents worth of accounting, so we need to drop in
* this case.
*/
old_size = other->end - other->start + 1;
num_extents = div64_u64(old_size + BTRFS_MAX_EXTENT_SIZE - 1,
BTRFS_MAX_EXTENT_SIZE);
old_size = new->end - new->start + 1;
num_extents += div64_u64(old_size + BTRFS_MAX_EXTENT_SIZE - 1,
BTRFS_MAX_EXTENT_SIZE);
if (div64_u64(new_size + BTRFS_MAX_EXTENT_SIZE - 1,
BTRFS_MAX_EXTENT_SIZE) > num_extents)
BTRFS_MAX_EXTENT_SIZE) >= num_extents)
return;
spin_lock(&BTRFS_I(inode)->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