[RFC PATCH 1/1] mm/mglru: age when the oldest generation is thrashing

From: Maxim Muzafarov

Date: Sun Nov 01 2026 - 11:05:07 EST


With four generations, should_run_aging() only runs when the window
shrinks. With every fault on MADV_RANDOM mapping landing in min_seq,
and the oldest generation refilling as fast as it is evicted,
min_seq never advances and pages in the generations above it are
never evicted.

Run aging when the oldest file generation has refaulted more pages than
the lruvec holds since it became the oldest.

Signed-off-by: Maxim Muzafarov <maxmuzaf@xxxxxxxxx>
---
mm/vmscan.c | 32 ++++++++++++++++++++++++++++----
1 file changed, 28 insertions(+), 4 deletions(-)

diff --git a/mm/vmscan.c b/mm/vmscan.c
index f11491ee9ed5..f59b29723ec9 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -4966,8 +4966,26 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
return scanned;
}

+static bool oldest_gen_thrashing(struct lruvec *lruvec, unsigned long *min_seq,
+ int swappiness, unsigned long evictable)
+{
+ int tier;
+ unsigned long refaulted = 0;
+ struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ int hist = lru_hist_from_seq(min_seq[LRU_GEN_FILE]);
+
+ if (swappiness == SWAPPINESS_ANON_ONLY)
+ return false;
+
+ for (tier = 0; tier < MAX_NR_TIERS; tier++)
+ refaulted += atomic_long_read(&lrugen->refaulted[hist][LRU_GEN_FILE][tier]);
+
+ return refaulted > evictable;
+}
+
static bool should_run_aging(struct lruvec *lruvec, unsigned long max_seq,
- struct scan_control *sc, int swappiness)
+ struct scan_control *sc, int swappiness,
+ unsigned long evictable)
{
DEFINE_MIN_SEQ(lruvec);

@@ -4975,6 +4993,9 @@ static bool should_run_aging(struct lruvec *lruvec, unsigned long max_seq,
if (evictable_min_seq(min_seq, swappiness) + MIN_NR_GENS > max_seq)
return true;

+ if (oldest_gen_thrashing(lruvec, min_seq, swappiness, evictable))
+ return true;
+
/* try to avoid aging, do gentle reclaim at the default priority */
if (sc->priority == DEF_PRIORITY)
return false;
@@ -4984,7 +5005,8 @@ static bool should_run_aging(struct lruvec *lruvec, unsigned long max_seq,
}

static long get_nr_to_scan(struct lruvec *lruvec, struct scan_control *sc,
- struct mem_cgroup *memcg, int swappiness)
+ struct mem_cgroup *memcg, int swappiness,
+ unsigned long *nr_evictable)
{
unsigned long nr_to_scan, evictable;
struct pglist_data *pgdat = lruvec_pgdat(lruvec);
@@ -5003,6 +5025,7 @@ static long get_nr_to_scan(struct lruvec *lruvec, struct scan_control *sc,
}

evictable = lruvec_evictable_size(lruvec, swappiness);
+ *nr_evictable = evictable;

/* try to scrape all its memory if this memcg was deleted */
if (!mem_cgroup_online(memcg))
@@ -5053,10 +5076,11 @@ static bool try_to_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)
{
bool need_rotate = false, should_age = false;
long nr_batch, nr_to_scan;
+ unsigned long evictable = 0;
int swappiness = get_swappiness(lruvec, sc);
struct mem_cgroup *memcg = lruvec_memcg(lruvec);

- nr_to_scan = get_nr_to_scan(lruvec, sc, memcg, swappiness);
+ nr_to_scan = get_nr_to_scan(lruvec, sc, memcg, swappiness, &evictable);
while (nr_to_scan > 0) {
int delta;
DEFINE_MAX_SEQ(lruvec);
@@ -5066,7 +5090,7 @@ static bool try_to_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)
break;
}

- if (should_run_aging(lruvec, max_seq, sc, swappiness)) {
+ if (should_run_aging(lruvec, max_seq, sc, swappiness, evictable)) {
if (try_to_inc_max_seq(lruvec, max_seq, swappiness, false))
need_rotate = true;
should_age = true;

base-commit: 24a19b953e2a3d0ea5a8c4951a0ab8f32720c907
--
2.56.0