Re: [PATCH v2 12/14] mm/shmem: fix truncate loop after boundary swapin failure
From: Baolin Wang
Date: Tue Oct 27 2026 - 21:31:28 EST
On 10/27/26 9:03 PM, David Hildenbrand (Arm) wrote:
If swapping in a large entry at the new EOF fails, shmem_undo_range()
leaves the entry in place. Its final scan skips the entry, then restarts
from the same start index and finds it again. Truncation can loop forever
while holding i_rwsem.
Right. But ...
Advance start past the skipped entry so that a restarted scan can finish,
as we already do for folios that cannot be split.
IIRC, we originally avoided advancing start because a concurrent swapin fault could split the boundary large swap entry via shmem_split_large_entry(), leaving small swap entries below the new start.
Sigh.
There doesn't seem to be a simple fix. We may need to swap in the large entry as shmem_get_partial_folio() does, and then split the large folio. I'll work on this as a follow-up.
Note that this does not fix the missing zeroing when boundary swapin fails,
it merely prevents the endless loop.
Fixes: 8a1968bd997f ("mm/shmem, swap: fix race of truncate and swap entry split")
Cc: stable@xxxxxxxxxxxxxxx
Acked-by: Kiryl Shutsemau (Meta) <kas@xxxxxxxxxx>
Tested-by: Gregory Price (Meta) <gourry@xxxxxxxxxx>
Signed-off-by: David Hildenbrand (Arm) <david@xxxxxxxxxx>
---
mm/shmem.c | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/mm/shmem.c b/mm/shmem.c
index 9a11de010ed7b..4ece296a1ccf1 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -1434,14 +1434,16 @@ static void shmem_undo_range(struct inode *inode, loff_t lstart, uoff_t lend,
order = shmem_confirm_swap(mapping, indices[i],
radix_to_swp_entry(folio));
/*
- * If found a large swap entry cross the end or start
- * border, skip it as the truncate_inode_partial_folio
- * above should have at least zerod its content once.
+ * Skip boundary entries when restarting
+ * the scan to avoid endless loops after
+ * swapin failure.
*/
if (order > 0) {
base = round_down(base, 1 << order);
- if (base < start || base + (1 << order) > end)
+ if (base < start || base + (1 << order) > end) {
+ start = max(start, base + (1 << order));
continue;
+ }
}
/* Swap was replaced by page or extended, retry */
index = base;