Re: [PATCH 6/7] mm/mglru: fix potential generation folio number leak
From: Baolin Wang
Date: Thu Aug 20 2026 - 04:55:18 EST
On 8/20/26 11:45 AM, Kairui Song wrote:
On Thu, Aug 20, 2026 at 9:52 AM Baolin Wang
<baolin.wang@xxxxxxxxxxxxxxxxx> wrote:
On 8/18/26 1:38 PM, Kairui Song via B4 Relay wrote:
From: Kairui Song <kasong@xxxxxxxxxxx>
Each generation of MGLRU accounts anon and file folio numbers
separately. The page table walker's update_batch_size() derives the
anon / file type of a folio from its current flags, but the page table
walk holds neither the lruvec lock nor the folio lock, so the type can
change during that period.
Right.
MADV_FREE's lazyfree path clears PG_swapbacked under the lruvec lock,
so the folio is no longer considered on the anon LRU list. Lazyfreed
folios can also be changed back to the anon list again. If the flip
lands between folio_update_gen()'s cmpxchg and the type read in
update_batch_size(), the batched delta pair is applied to the wrong
type. The anon and file generation counters then carry phantom deltas
that nothing reconciles, permanently skewing lrugen->nr_pages and the
reclaim budgets derived from it.
But I think the problem occurs between update_batch_size() and
sort_folio(). update_batch_size() only updates the anon or file folio
statistics, while sort_folio() moves promoted folios to the
corresponding type's list:
/* promoted */
if (gen != lru_gen_from_seq(lrugen->min_seq[type])) {
list_move(&folio->lru, &lrugen->folios[gen][type][zone]);
return true;
}
If the folio's anon/file type changes between these two steps (e.g., a
lazyfree folio), it would lead to what you described: "The anon and file
generation counters then carry phantom deltas that nothing reconciles,
permanently skewing lrugen->nr_pages and the reclaim budgets derived
from it."
Actually no, the counters follow eventual consistency (note the word
"permanently"), we are fine with a drift as long as it will eventually
be corrected. Lazy promotions creates counter drift from the physical
location, but that is actually fixed by the sort_folio.
Now, for the type issue, use the lazyfree case as example (I think
that's actually the only place,), lru_lazyfree will remove the folio
form lruvec before marking it !PG_swapbacked, so during that removal
period, the gen bits are zero (folio's gen == -1), so any lazy
promotion CAS won't touch the counter, and only folio_update_gen will
do it since folio_inc_gen only handles on list folios. The
PG_swapbacked clearing in lazyfree only happens on folio wich has gen
== -1 (off-list). And it makes sense since update PG_swapbacked need
to update the counter and move the folio.
And if the CAS happends before the list removal, the list removal, the
folio is on the anon list, so the CAS is moving a folio in the anon
list, folio_update_gen will call update_batch_size asking it to update
the anon counters, we are fine after this commit. (Before this commit,
the CAS is moving a folio in the anon list but update_batch_size may
occur on file coutners). The list removal will update the anon
counter, and the subsequent list addition will account for the right
file counter.
And if the CAS happens after the list add, we are still fine since the
file counter is charged and sees a file type here.
Thanks for the expalnation. Now I see the problem and I think your are right.
(This involves the combination of the gen counter and the folio's type, so it would be better to have a diagram describing the race, otherwise it's indeed hard to follow.)