Re: [PATCH v3 4/4] mm/mglru: fix anon-only reclaim evicting file pages when swappiness=max

From: Ridong Chen

Date: Thu Jul 23 2026 - 22:23:58 EST




On 7/23/2026 4:57 PM, Barry Song wrote:
On Thu, Jul 23, 2026 at 12:58 PM Ridong <ridong.chen@xxxxxxxxx> wrote:

From: Ridong Chen <chenridong@xxxxxxxxxx>

The previous patch fixed this issue for the traditional LRU. The same
problem exists in MGLRU [1]: when swappiness=max (SWAPPINESS_ANON_ONLY)
is set, reclaim is expected to evict anonymous pages exclusively, but
file pages can still be reclaimed when anonymous pages cannot be
reclaimed (e.g. no swap and no demotion target).

With SWAPPINESS_ANON_ONLY, get_type_to_scan() always returns
LRU_GEN_ANON and for_each_evictable_type() only iterates the anon type.
But if get_nr_to_scan() does not bail out, evict_folios() still runs and
isolate_folios() falls back to scanning file pages in the !scanned case:

shrink_one
try_to_shrink_lruvec
get_swappiness // returns SWAPPINESS_ANON_ONLY for swappiness=max
get_nr_to_scan
evict_folios
isolate_folios // falls back to file type when !scanned


I am not convinced this is correct. Although we do have `type = !type`,
when `swappiness == 201`, `for_each_evictable_type()` stops iterating
after trying a single type, so there is no opportunity to fall back.

#define for_each_evictable_type(type, swappiness) \
for ((type) = min_type(swappiness); (type) <=
max_type(swappiness); (type)++)


You're right. In my initial version, the intention was to bail out early and avoid unnecessary work.

I recall that during your review of Kairui's series, you sent a patch that used a fallback mechanism with type = !type. I was thinking that this could become an issue if we call isolate_folios() when swappiness is set to max.

So I'm really curious what's happening here.

static int isolate_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
struct scan_control *sc, int swappiness,
struct list_head *list, int *isolated,
int *isolate_type, int *isolate_scanned)
{
int i;
int total_scanned = 0;
int type = get_type_to_scan(lruvec, swappiness);

for_each_evictable_type(i, swappiness) {
int scanned;
int tier = get_tier_idx(lruvec, type);

scanned = scan_folios(nr_to_scan, lruvec, sc,
type, tier, list, isolated);

...
if (!scanned)
type = !type;
}

return total_scanned;
}

since both min and max types are anon:
#define min_type(swappiness) (!(swappiness))
#define max_type(swappiness) ((swappiness) < SWAPPINESS_ANON_ONLY)

I guess the real problem only occurs when `may_swap` is false?
I added some trace logs to verify this behavior. If we don't make get_nr_to_scan() return 0 when swappiness=max, it won't fall back to scanning file pages, but it will still perform useless scanning that yields no reclaim.

Log:
# echo "64M swappiness=max" > memory.reclaim
[ 78.914972] vmscan: in isolate_folios swappness 201
[ 78.916841] vmscan: call scan_folios type 0
[ 78.918311] vmscan: fall back 1 <- can't call scan_folios, loop ends.
[ 78.919479] vmscan: out isolate_folios total_scanned 0
[...]

# cat memory.stat | grep proactive
pgdemote_proactive 0
pgsteal_proactive 0
pgscan_proactive 1808

I will update the commit message to clarify that get_nr_to_scan() returns 0 to avoid this useless work.

--
Best regards
Ridong