Re: [PATCH] mm/mglru: force aging the lruvec when min_seq pinned
From: Zhaoyang Huang
Date: Thu Jul 30 2026 - 05:34:34 EST
On Thu, Jul 30, 2026 at 5:06 PM Barry Song <baohua@xxxxxxxxxx> wrote:
>
> 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?
Yes. As described in commit message, the orphan folio is an abnormal
one as it is on page cache but has folio->refcount=0 which makes it
isolated and putback to lru forever and finally be the only one in
min_seq. IMO, MGLRU should keep make progress with bringing this
orphan folio together with.
>
> 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