Re: [PATCH 12/16] mm/shmem: fix truncate hang in large swap entry
From: David Hildenbrand (Arm)
Date: Tue Oct 27 2026 - 06:49:20 EST
On 10/27/26 09:59, Baolin Wang wrote:
>
>
> On 10/26/26 5:40 PM, David Hildenbrand (Arm) wrote:
>> Truncating a tmpfs file or memfd to a page-aligned size inside a large
>> swap entry can loop indefinitely in shmem_undo_range(). The task holds
>> the inode lock and cannot be killed.
>>
>> A large swap entry represents a whole swapped-out folio. For example,
>> truncate a file whose pages 0-7 share one swap entry to four pages:
>>
>> [ 0 1 2 3 | 4 5 6 7 ]
>> keep | discard
>> ^
>> new EOF, start = 4
>>
>> i_size has already been updated when boundary handling asks
>> shmem_get_partial_folio() for page 4. Since it is swapped out, the helper
>> calls shmem_get_folio() with SGP_READ. That rejects page 4 with -EINVAL
>> because its starting offset equals i_size. Nothing is read back.
>>
>> The final scan cannot free the entry without losing pages 0-3. It skips
>> it, restarts from start = 4, and finds it again indefinitely. An unaligned
>> truncate avoids this particular failure because the requested page starts
>> below i_size. Folios already in the page-cache cannot trigger it as the
>> direct lookup will return them beyond EOF.
>>
>> Fix it by letting shmem_get_partial_folio() call shmem_swapin_folio()
>> directly for swap entries. This bypasses the read path's i_size check,
>> allowing the existing boundary handling to zero and split the folio. If
>> swap-in returns a smaller folio, it also splits the swap entry so the final
>> scan can free the entries beyond EOF. Retry the lookup if the entry
>> changes during swap-in.
>
> Make sense to me.
>
>> Handling swap-in allocation failures remains broken and is unchanged by
>> this commit. Even when shmem_swapin_folio() correctly returns -ENOMEM
>> instead of -EEXIST, shmem_get_partial_folio() returns NULL and
>> shmem_undo_range() skips boundary handling. If the large swap entry
>> remains intact, truncation can still loop in the final scan, while hole
>> punching can return without zeroing the boundary.
>>
>> This may read an entire large folio back from swap while holding
>> i_rwsem, only to discard part of it. Avoiding that I/O would require
>> splitting both the swap entry and any large folio still in the swap
>> cache. That, however, is much bigger work.
>
> But I think this case is extremely rare, where every attempt to call
> shmem_split_large_entry() to split the large swap entry in the loop fails. At
> that point, the system is already in serious trouble, where even OOM kill cannot
> free up memory for the xarray allocation. Hard to believe this can actually
> happen in practice:).
So, it's fairly easy to reproduce with memcgs. But yeah, I assume this is a
corner case. (hopefully :D )
--
Cheers,
David