Re: [PATCH 1/5] mm/huge_memory: do not touch frozen folios in deferred_split_isolate()
From: David Hildenbrand (Arm)
Date: Thu Aug 27 2026 - 11:56:27 EST
On 8/27/26 17:38, Zi Yan wrote:
> On 27 Aug 2026, at 11:23, David Hildenbrand (Arm) wrote:
>
>> On 8/26/26 18:20, Kiryl Shutsemau wrote:
>>> From: "Kiryl Shutsemau (Meta)" <kas@xxxxxxxxxx>
>>>
>>> deferred_split_isolate() probes each queued folio with folio_try_get().
>>> folio_try_get() failure is treated as a lost race with folio_put(): clear
>>> PG_partially_mapped, correct MTHP_STAT_NR_ANON_PARTIALLY_MAPPED, take
>>> the folio off the queue.
>>>
>>> The folio_put() race is the most common case for !folio_try_get(), but
>>> it is not the only option. Another scenario is folio_ref_freeze().
>>>
>>> A zero refcount in such cases does not mean the folio is going away. It
>>> means "don't touch me" and current deferred_split_isolate() doesn't
>>> respect it. It can lead to unqueueing folios from the deferred list for
>>> no reason:
>>
>> Yes.
>>
>>>
>>> CPU 0 CPU 1
>>> --------------------------- ------------------------------
>>> freeze a mapped folio deferred_split_scan()
>>> folio_ref_freeze() folio_try_get() fails
>>> folio_clear_partially_mapped()
>>> NR_ANON_PARTIALLY_MAPPED--
>>> folio off the queue
>>> give up, put it back
>>> folio_ref_unfreeze()
>>>
>>> The folio is still partially mapped, but it is no longer a split candidate.
>>> Nothing queues it again until part of it is unmapped once more.
>>>
>>> Skip the folio instead: whoever freezes the folio, owns it and owner is
>>> responsible for its fate. It also covers the folio_put() case:
>>> __folio_put() unqueues the folio via folio_unqueue_deferred_split().
>>>
>>> Reported-by: Lance Yang <lance.yang@xxxxxxxxx>
>>> Link: https://lore.kernel.org/all/20260824131224.73344-1-lance.yang@xxxxxxxxx/
>>
>> Fixes?
>>
>>> Assisted-by: Claude-Code:claude-opus-5
>>> Signed-off-by: Kiryl Shutsemau (Meta) <kas@xxxxxxxxxx>
>>> ---
>>> mm/huge_memory.c | 19 ++++---------------
>>> 1 file changed, 4 insertions(+), 15 deletions(-)
>>>
>>> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
>>> index ced400f72d43..6281ed993243 100644
>>> --- a/mm/huge_memory.c
>>> +++ b/mm/huge_memory.c
>>> @@ -4590,22 +4590,11 @@ static enum lru_status deferred_split_isolate(struct list_head *item,
>>> struct folio *folio = container_of(item, struct folio, _deferred_list);
>>> struct list_head *freeable = cb_arg;
>>>
>>> - if (folio_try_get(folio)) {
>>> - list_lru_isolate_move(lru, item, freeable);
>>> - return LRU_REMOVED;
>>> - }
>>> + /* Lost race to folio_put() or the folio is under folio_ref_freeze() */
>>> + if (!folio_try_get(folio))
>>> + return LRU_SKIP;
>>>
>>> - /*
>>> - * We lost race with folio_put(). Read folio state before the
>>> - * isolate: folio_unqueue_deferred_split() checks list_empty()
>>> - * locklessly, so once removed the folio can be freed any time.
>>> - */
>>> - if (folio_test_partially_mapped(folio)) {
>>> - folio_clear_partially_mapped(folio);
>>> - mod_mthp_stat(folio_order(folio),
>>> - MTHP_STAT_NR_ANON_PARTIALLY_MAPPED, -1);
>>> - }
>>> - list_lru_isolate(lru, item);
>>> + list_lru_isolate_move(lru, item, freeable);
>>> return LRU_REMOVED;
>>> }
>>>
>>
>> Who will clean up the stats that we used to clean up? That should be mentioned
>> in the commit log, otherwise it looks like some piece of the puzzle is missing.
>
> It is kinda implied in the last paragraph in the commit log (copied below),
> but I agree that stating it explicitly is better.
>
> |> Skip the folio instead: whoever freezes the folio, owns it and owner is
> |> responsible for its fate. It also covers the folio_put() case:
> |> __folio_put() unqueues the folio via folio_unqueue_deferred_split().
yes, I read that but was not convinced by it that all existing freezing code
would handle it correctly. So spelling that out more clearly would be best.
--
Cheers,
David