Re: [PATCH] mm/mglru: force aging the lruvec when min_seq pinned
From: Barry Song
Date: Thu Jul 30 2026 - 05:12:06 EST
On Thu, Jul 30, 2026 at 3:24 PM zhaoyang.huang
<zhaoyang.huang@xxxxxxxxxx> wrote:
>
> From: Zhaoyang Huang <zhaoyang.huang@xxxxxxxxxx>
>
> This submission addresses an issue where the memcg MGLRU's min_seq is
> pinned by an orphan page—a page that resides in the page cache but
> lacks a corresponding reference count, while the number of GENs has not
> yet met the criteria of aging. I believe such pages should be detected
> and handled elsewhere rather than affecting the LRU in this way(legacy
> LRU could move forward by bringing this kind of folio along with).
> Therefore, I propose moving these folios to the next generation when
> isolation makes no progress at all.
>
> Signed-off-by: Zhaoyang Huang <zhaoyang.huang@xxxxxxxxxx>
> ---
> include/linux/mmzone.h | 6 ++++++
> mm/vmscan.c | 45 +++++++++++++++++++++++++++++++++++++++++-
> 2 files changed, 50 insertions(+), 1 deletion(-)
>
> diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
> index 9adb2ad21da5..b063c56ea30c 100644
> --- a/include/linux/mmzone.h
> +++ b/include/linux/mmzone.h
> @@ -434,6 +434,12 @@ enum lruvec_flags {
> */
> LRUVEC_CGROUP_CONGESTED,
> LRUVEC_NODE_CONGESTED,
> + /*
> + * MGLRU: eviction failed to isolate from the oldest generation once
> + * while the window was at MAX_NR_GENS. A second consecutive failure
> + * forces aging so residual folios cannot pin min_seq indefinitely.
> + */
> + LRUVEC_MGLRU_ISOLATE_FAILED,
> };
>
> #endif /* !__GENERATING_BOUNDS_H */
> diff --git a/mm/vmscan.c b/mm/vmscan.c
> index bd1b1aa12581..c18db08c5e10 100644
> --- a/mm/vmscan.c
> +++ b/mm/vmscan.c
> @@ -175,6 +175,8 @@ struct scan_control {
> unsigned int immediate;
> unsigned int file_taken;
> unsigned int taken;
> + /* folios isolated by MGLRU eviction in the current lruvec pass */
> + unsigned int isolated;
> } nr;
>
> /* for recording the reclaimed slab by now */
> @@ -4748,6 +4750,7 @@ static int scan_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
> trace_mm_vmscan_lru_isolate(sc->reclaim_idx, sc->order, scan_batch,
> scanned, skipped, isolated,
> type ? LRU_INACTIVE_FILE : LRU_INACTIVE_ANON);
> + sc->nr.isolated += isolated;
> if (type == LRU_GEN_FILE)
> sc->nr.file_taken += isolated;
> /*
> @@ -4998,6 +5001,11 @@ static bool try_to_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)
> long nr_to_scan;
> unsigned long scanned = 0;
> int swappiness = get_swappiness(lruvec, sc);
> + bool attempted = false;
> + bool rotated = false;
> +
> + /* per-lruvec pass; sc is shared across lruvecs during reclaim */
> + sc->nr.isolated = 0;
>
> while (true) {
> int delta;
> @@ -5006,6 +5014,7 @@ static bool try_to_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)
> if (nr_to_scan <= 0)
> break;
>
> + attempted = true;
> delta = evict_folios(nr_to_scan, lruvec, sc, swappiness);
> if (!delta)
> break;
> @@ -5020,6 +5029,40 @@ static bool try_to_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)
> cond_resched();
> }
>
> + /*
> + * Eviction isolated nothing while the sliding window is already at
> + * MAX_NR_GENS. In that state should_run_aging() is false, so
> + * get_nr_to_scan() never calls try_to_inc_max_seq(). Residual folios
> + * that fail isolate_folio() can pin min_seq and hide reclaimable
> + * folios in younger generations.
> + *
> + * Require two consecutive isolate failures on this lruvec before
> + * forcing aging, so a single no-progress batch does not rotate the
> + * window. Successful isolation clears the sticky bit.
> + *
> + * Key off sc->nr.isolated, not nr_reclaimed: folios isolated but
> + * rejected by shrink_folio_list() are put back with PG_active and do
> + * not pin the oldest generation.
> + */
> + if (attempted && !sc->nr.isolated) {
> + DEFINE_MAX_SEQ(lruvec);
> + DEFINE_MIN_SEQ(lruvec);
> +
> + if (evictable_min_seq(min_seq, swappiness) + MIN_NR_GENS < max_seq) {
> + if (test_and_set_bit(LRUVEC_MGLRU_ISOLATE_FAILED,
> + &lruvec->flags)) {
> + rotated = try_to_inc_max_seq(lruvec, max_seq,
> + swappiness, false);
> + clear_bit(LRUVEC_MGLRU_ISOLATE_FAILED,
> + &lruvec->flags);
> + }
> + } else {
> + clear_bit(LRUVEC_MGLRU_ISOLATE_FAILED, &lruvec->flags);
> + }
> + } else if (sc->nr.isolated) {
> + clear_bit(LRUVEC_MGLRU_ISOLATE_FAILED, &lruvec->flags);
> + }
This is very weird. This seems like using a sledgehammer to kill a fly :-)
Do you mean that isolate_folio() fails, the folio is put back
into the oldest generation, and the next time it is scanned, it
still fails to be isolated? As a result, scan_folios() makes
no progress at all?
And is it because the oldest generation contains only this
folio, leaving no other folios for scan_folios() to scan?
Why can't you simply promote the folio?
Do you know why isolate_folio() keeps failing? I suspect it's
because folio_try_get(folio) fails repeatedly?
static bool isolate_folio(struct lruvec *lruvec, struct folio *folio,
struct scan_control *sc)
{
bool success;
/* raced with release_pages() */
if (!folio_try_get(folio))
return false;
/* raced with another isolation */
if (!folio_test_clear_lru(folio)) {
folio_put(folio);
return false;
}
...
return true;
}
Best Regards
Barry