btrfs: avoid page_lockend underflow in btrfs_punch_hole_lock_range()
[ Upstream commit bc2dbc4983afedd198490cca043798f57c93e9bf ]
[BUG]
When running btrfs/004 with 4K fs block size and 64K page size,
sometimes fsstress workload can take 100% CPU for a while, but not long
enough to trigger a 120s hang warning.
[CAUSE]
When such 100% CPU usage happens, btrfs_punch_hole_lock_range() is
always in the call trace.
One example when this problem happens, the function
btrfs_punch_hole_lock_range() got the following parameters:
lock_start = 4096, lockend = 20469
Then we calculate @page_lockstart by rounding up lock_start to page
boundary, which is 64K (page size is 64K).
For @page_lockend, we round down the value towards page boundary, which
result 0. Then since we need to pass an inclusive end to
filemap_range_has_page(), we subtract 1 from the rounded down value,
resulting in (u64)-1.
In the above case, the range is inside the same page, and we do not even
need to call filemap_range_has_page(), not to mention to call it with
(u64)-1 at the end.
This behavior will cause btrfs_punch_hole_lock_range() to busy loop
waiting for irrelevant range to have its pages dropped.
[FIX]
Calculate @page_lockend by just rounding down @lockend, without
decreasing the value by one. So @page_lockend will no longer overflow.
Then exit early if @page_lockend is no larger than @page_lockstart.
As it means either the range is inside the same page, or the two pages
are adjacent already.
Finally only decrease @page_lockend when calling filemap_range_has_page().
Fixes: 0528476b6a
("btrfs: fix the filemap_range_has_page() call in btrfs_punch_hole_lock_range()")
Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: Qu Wenruo <wqu@suse.com>
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
1845e03ea7
commit
4a1b61f910
@@ -2225,15 +2225,20 @@ static void btrfs_punch_hole_lock_range(struct inode *inode,
|
||||
* will always return true.
|
||||
* So here we need to do extra page alignment for
|
||||
* filemap_range_has_page().
|
||||
*
|
||||
* And do not decrease page_lockend right now, as it can be 0.
|
||||
*/
|
||||
const u64 page_lockstart = round_up(lockstart, PAGE_SIZE);
|
||||
const u64 page_lockend = round_down(lockend + 1, PAGE_SIZE) - 1;
|
||||
const u64 page_lockend = round_down(lockend + 1, PAGE_SIZE);
|
||||
|
||||
while (1) {
|
||||
truncate_pagecache_range(inode, lockstart, lockend);
|
||||
|
||||
lock_extent(&BTRFS_I(inode)->io_tree, lockstart, lockend,
|
||||
cached_state);
|
||||
/* The same page or adjacent pages. */
|
||||
if (page_lockend <= page_lockstart)
|
||||
break;
|
||||
/*
|
||||
* We can't have ordered extents in the range, nor dirty/writeback
|
||||
* pages, because we have locked the inode's VFS lock in exclusive
|
||||
@@ -2245,7 +2250,7 @@ static void btrfs_punch_hole_lock_range(struct inode *inode,
|
||||
* we do, unlock the range and retry.
|
||||
*/
|
||||
if (!filemap_range_has_page(inode->i_mapping, page_lockstart,
|
||||
page_lockend))
|
||||
page_lockend - 1))
|
||||
break;
|
||||
|
||||
unlock_extent(&BTRFS_I(inode)->io_tree, lockstart, lockend,
|
||||
|
Reference in New Issue
Block a user