[PATCH 1/5] mm/huge_memory: do not touch frozen folios in deferred_split_isolate()
From: Kiryl Shutsemau
Date: Wed Aug 26 2026 - 12:25:14 EST
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:
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/
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;
}
--
2.54.0