Re: [RFC PATCH v5 04/14] mm/mglru: lift the forced protection of reclaimer
From: KunWu Chan
Date: Wed Oct 28 2026 - 00:14:47 EST
On Fri, Oct 23, 2026 at 6:02 AM Barry Song (Xiaomi) <baohua@xxxxxxxxxx> wrote:
>
> From: Kairui Song <kasong@xxxxxxxxxxx>
>
> When a type is drained down to MIN_NR_GENS gens, the reclaimer will
> fail to make any progress on that type without aging.
>
> However aging comes with a cost, and it will take time to finish. Even
> if we do aging more proactively, when under great pressure, gens will
> still be drained very fast so the new gens produced by aging can't catch
> up with the consumption. With MIN_NR_GENS setting blocking the reclaim,
> we will run into OOM. This OOM can be reproduced by the previous test
> program [1] used for fixing another aging problem [2]. When running in
> great pressure, certain workloads will keep seeing premature OOM with
> MGLRU, and never see any OOM with classical LRU.
>
> This might get worse if we implement async aging, or avoid hotness
> inversion in aging, as they will slow down the aging process even more.
>
> To fix this, let's first drop the get_nr_gens() == MIN_NR_GENS early
> return and let the raised-priority scan bound cover max_seq itself.
>
> Mapped folios will still survive as the rmap PTE check will keep them.
> Unmapped cache folios may thrashing more when under great pressure,
> which is really a problem that should be fixed with refault distance
> and frequecy aware promotoin instead [3].
>
> Link: https://github.com/ryncsn/emm-test-project/tree/master/file-anon-mix-pressure [1]
> Link: https://lore.kernel.org/linux-mm/20260428-mglru-reclaim-v7-0-02fabb92dc43@xxxxxxxxxxx/ [2]
> Link: https://lore.kernel.org/linux-mm/20260502-mglru-fg-v1-0-913619b014d9@xxxxxxxxxxx/ [3]
> Signed-off-by: Kairui Song <kasong@xxxxxxxxxxx>
> Signed-off-by: Barry Song (Xiaomi) <baohua@xxxxxxxxxx>
> ---
> mm/vmscan.c | 34 +++++++++++++++++++++++++---------
> 1 file changed, 25 insertions(+), 9 deletions(-)
>
> diff --git a/mm/vmscan.c b/mm/vmscan.c
> index d7eb15533de1..2cccaa5dd4e7 100644
> --- a/mm/vmscan.c
> +++ b/mm/vmscan.c
> @@ -4834,7 +4834,7 @@ void lru_gen_reparent_memcg(struct mem_cgroup *memcg, struct mem_cgroup *parent,
> ******************************************************************************/
>
> static bool sort_folio(struct lruvec *lruvec, struct folio *folio, struct scan_control *sc,
> - int min_gen, int tier_idx)
> + int min_gen, int max_gen, int tier_idx)
> {
> int gen = folio_lru_gen(folio);
> int type = folio_is_file_lru(folio);
> @@ -4851,12 +4851,22 @@ static bool sort_folio(struct lruvec *lruvec, struct folio *folio, struct scan_c
> if (!folio_evictable(folio))
> return false;
>
> - /* promoted */
> + /* promoted, or moved due to reparenting. */
> if (gen != min_gen) {
> list_move(&folio->lru, &lrugen->folios[gen][type][zone]);
> return true;
> }
>
> + /*
> + * We are reclaiming the last gen left. Folios in this gen can no
> + * longer be promoted or protected, so just isolate them. The one
> + * exception is lru_gen_reparent_memcg(), which is handled above.
> + */
> + if (min_gen == max_gen) {
> + VM_WARN_ON_ONCE(zone > sc->reclaim_idx);
> + return false;
> + }
> +
> /* protected */
> if (tier > tier_idx || refs + workingset == BIT(LRU_REFS_WIDTH) + 1) {
> gen = folio_inc_gen(lruvec, folio, min_gen);
> @@ -4911,15 +4921,16 @@ static int scan_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
> struct list_head *list, int *isolatedp)
> {
> int i;
> - int gen;
> enum node_stat_item item;
> int sorted = 0;
> int scanned = 0;
> int isolated = 0;
> int skipped = 0;
> + int gen, max_gen;
> unsigned long min_seq;
> unsigned long remaining = nr_to_scan;
> struct lru_gen_folio *lrugen = &lruvec->lrugen;
> + DEFINE_MAX_SEQ(lruvec);
>
> VM_WARN_ON_ONCE(nr_to_scan > MAX_LRU_BATCH);
> VM_WARN_ON_ONCE(!list_empty(list));
> @@ -4932,18 +4943,24 @@ static int scan_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
> if (sc->priority == DEF_PRIORITY)
> min_seq = lrugen->min_seq[type];
> else
> - min_seq = lruvec_populated_min_seq(lruvec, type, lrugen->max_seq - MIN_NR_GENS + 1);
> -
> - if (get_nr_gens(lruvec, type) == MIN_NR_GENS)
> - return 0;
> + min_seq = lruvec_populated_min_seq(lruvec, type, lrugen->max_seq);
>
> gen = lru_gen_from_seq(min_seq);
> + max_gen = lru_gen_from_seq(max_seq);
> +
> for (i = MAX_NR_ZONES; i > 0; i--) {
> LIST_HEAD(moved);
> int skipped_zone = 0;
> int zone = (sc->reclaim_idx + i) % MAX_NR_ZONES;
> struct list_head *head = &lrugen->folios[gen][type][zone];
>
> + /*
> + * If there is only one gen left, rotating the unreclaimable
> + * zone become meaningless, just skip them.
> + */
> + if (gen == max_gen && zone > sc->reclaim_idx)
> + continue;
> +
> while (!list_empty(head)) {
> struct folio *folio = lru_to_folio(head);
> int delta = folio_nr_pages(folio);
> @@ -4954,8 +4971,7 @@ static int scan_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
> VM_WARN_ON_ONCE_FOLIO(folio_zonenum(folio) != zone, folio);
>
> scanned += delta;
> -
> - if (sort_folio(lruvec, folio, sc, gen, tier))
> + if (sort_folio(lruvec, folio, sc, gen, max_gen, tier))
> sorted += delta;
> else if (isolate_folio(lruvec, folio, sc)) {
> list_add(&folio->lru, list);
> --
> 2.34.1
>
Removing the MIN_NR_GENS early return allows reclaim to make progress
when only one generation remains. Bypassing promotion and protection
in that case lets reclaim attempt to isolate those folios, while
scan_folios() skips zones outside reclaim_idx.
Reviewed-by: Kunwu Chan <kunwu.chan@xxxxxxxxx>