xfs: split indlen reservations fairly when under reserved

Certain workoads that punch holes into speculative preallocation can
cause delalloc indirect reservation splits when the delalloc extent is
split in two. If further splits occur, an already short-handed extent
can be split into two in a manner that leaves zero indirect blocks for
one of the two new extents. This occurs because the shortage is large
enough that the xfs_bmap_split_indlen() algorithm completely drains the
requested indlen of one of the extents before it honors the existing
reservation.

This ultimately results in a warning from xfs_bmap_del_extent(). This
has been observed during file copies of large, sparse files using 'cp
--sparse=always.'

To avoid this problem, update xfs_bmap_split_indlen() to explicitly
apply the reservation shortage fairly between both extents. This smooths
out the overall indlen shortage and defers the situation where we end up
with a delalloc extent with zero indlen reservation to extreme
circumstances.

Reported-by: Patrick Dung <mpatdung@gmail.com>
Signed-off-by: Brian Foster <bfoster@redhat.com>
Reviewed-by: Darrick J. Wong <darrick.wong@oracle.com>
Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
This commit is contained in:
Brian Foster 2017-02-13 22:48:30 -08:00 committed by Darrick J. Wong
parent 0e339ef855
commit 75d65361cf
1 changed files with 43 additions and 18 deletions

View File

@ -4795,34 +4795,59 @@ xfs_bmap_split_indlen(
xfs_filblks_t len2 = *indlen2; xfs_filblks_t len2 = *indlen2;
xfs_filblks_t nres = len1 + len2; /* new total res. */ xfs_filblks_t nres = len1 + len2; /* new total res. */
xfs_filblks_t stolen = 0; xfs_filblks_t stolen = 0;
xfs_filblks_t resfactor;
/* /*
* Steal as many blocks as we can to try and satisfy the worst case * Steal as many blocks as we can to try and satisfy the worst case
* indlen for both new extents. * indlen for both new extents.
*/ */
while (nres > ores && avail) { if (ores < nres && avail)
nres--; stolen = XFS_FILBLKS_MIN(nres - ores, avail);
avail--; ores += stolen;
stolen++;
} /* nothing else to do if we've satisfied the new reservation */
if (ores >= nres)
return stolen;
/* /*
* The only blocks available are those reserved for the original * We can't meet the total required reservation for the two extents.
* extent and what we can steal from the extent being removed. * Calculate the percent of the overall shortage between both extents
* If this still isn't enough to satisfy the combined * and apply this percentage to each of the requested indlen values.
* requirements for the two new extents, skim blocks off of each * This distributes the shortage fairly and reduces the chances that one
* of the new reservations until they match what is available. * of the two extents is left with nothing when extents are repeatedly
* split.
*/ */
while (nres > ores) { resfactor = (ores * 100);
if (len1) { do_div(resfactor, nres);
len1--; len1 *= resfactor;
nres--; do_div(len1, 100);
len2 *= resfactor;
do_div(len2, 100);
ASSERT(len1 + len2 <= ores);
ASSERT(len1 < *indlen1 && len2 < *indlen2);
/*
* Hand out the remainder to each extent. If one of the two reservations
* is zero, we want to make sure that one gets a block first. The loop
* below starts with len1, so hand len2 a block right off the bat if it
* is zero.
*/
ores -= (len1 + len2);
ASSERT((*indlen1 - len1) + (*indlen2 - len2) >= ores);
if (ores && !len2 && *indlen2) {
len2++;
ores--;
}
while (ores) {
if (len1 < *indlen1) {
len1++;
ores--;
} }
if (nres == ores) if (!ores)
break; break;
if (len2) { if (len2 < *indlen2) {
len2--; len2++;
nres--; ores--;
} }
} }