From: sashiko-bot@kernel.org
To: "Barry Song (Xiaomi)" <baohua@kernel.org>,
akpm@linux-foundation.org, linux-mm@kvack.org
Cc: axelrasmussen@google.com, baolin.wang@linux.alibaba.com,
baoquan.he@linux.dev, hannes@cmpxchg.org, hui.zhu@linux.dev,
kasong@tencent.com, kunwu.chan@gmail.com, liam@infradead.org,
lianux.mm@gmail.com, liuxiang.277@bytedance.com,
lyugaofei@xiaomi.com, mhocko@kernel.org, mhocko@suse.com,
qi.zheng@linux.dev, ridong.chen@linux.dev, rppt@kernel.org,
ryncsn@gmail.com, shakeel.butt@linux.dev, surenb@google.com,
wangzicheng@honor.com, weixugc@google.com, yuanchu@google.com,
zhangbo56@xiaomi.com, zhuhui@kylinos.cn,
sidchintamaneni@gmail.com
Subject: Re: [PATCH v8 11/16] mm/mglru: Run aging when one type has no populatable folios
Date: Sat, 14 Nov 2026 02:02:17 +0000 [thread overview]
Message-ID: <sashiko-outbox-214030@kernel.org> (raw)
In-Reply-To: <20261113185541.70203-12-baohua@kernel.org>
Thank you for your contribution! Sashiko AI review found 2 potential
issues to consider:
- [High] mm/mglru: premature loop exit after aging in
try_to_shrink_lruvec()
- [Medium] mm/mglru: late max_seq read in try_to_shrink_lruvec() causes
redundant aging
--
commit 9c58557be00bbe7f49dfd8383a560ebff484751b
Author: Barry Song (Xiaomi) <baohua@kernel.org>
mm/mglru: Run aging when one type has no populatable folios
This commit moves the aging check in try_to_shrink_lruvec() after
evict_folio_lists() and triggers aging once per invocation when a folio
type has remaining scan budget but no populatable folios were scanned.
> diff --git a/mm/vmscan.c b/mm/vmscan.c
> index 59957fab915b7..9071f8889b5c4 100644
> --- a/mm/vmscan.c
> +++ b/mm/vmscan.c
> @@ -5109,7 +5109,8 @@ static bool lru_gen_imbalanced(struct lruvec *lruvec, struct scan_control *sc,
> }
>
> static bool should_run_aging(struct lruvec *lruvec, unsigned long max_seq,
> - struct scan_control *sc, int swappiness)
> + struct scan_control *sc, int swappiness,
> + bool no_populatable)
> {
> DEFINE_MIN_SEQ(lruvec);
>
> @@ -5121,6 +5122,13 @@ static bool should_run_aging(struct lruvec *lruvec, unsigned long max_seq,
> if (sc->priority == DEF_PRIORITY)
> return false;
>
> + /*
> + * Run aging if scan budget remains but no folios
> + * in populatable generations are available to scan.
> + */
> + if (no_populatable)
> + return true;
> +
> /* better to run aging even though eviction is still possible */
> if (evictable_min_seq(min_seq, swappiness) + MIN_NR_GENS == max_seq)
> return true;
> @@ -5208,6 +5216,13 @@ static bool try_to_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)
> bool need_rotate = false, should_age = false;
> int swappiness = get_swappiness(lruvec, sc);
> struct mem_cgroup *memcg = lruvec_memcg(lruvec);
> + /*
> + * Avoid aging the lagging type under extreme swappiness,
> + * as reclaim of the unpreferred type is disabled until
> + * DEF_PRIORITY / 2.
> + */
> + bool allow_no_populatable_age = (sc->priority <= DEF_PRIORITY / 2) ||
> + (swappiness > MIN_SWAPPINESS + 1 && swappiness < MAX_SWAPPINESS);
>
> /*
> * Proactive reclaim initiated by userspace for anonymous memory only.
> @@ -5225,22 +5240,19 @@ static bool try_to_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)
> lru_gen_prepare_scan(lruvec, sc, memcg, swappiness, nr);
>
> while (nr[LRU_GEN_ANON] > 0 || nr[LRU_GEN_FILE] > 0) {
> - DEFINE_MAX_SEQ(lruvec);
> + unsigned long orig_file, orig_anon;
> + unsigned long max_seq;
> + bool no_populatable;
> + long scanned;
>
> if (mem_cgroup_below_min(sc->target_mem_cgroup, memcg)) {
> need_rotate = true;
> break;
> }
>
> - if (should_run_aging(lruvec, max_seq, sc, swappiness)) {
> - trace_mm_mglru_aging(lruvec, sc, swappiness);
> - if (try_to_inc_max_seq(lruvec, max_seq, swappiness, false))
> - need_rotate = true;
> - should_age = true;
> - }
> -
> - if (!evict_folio_lists(nr, lruvec, sc, swappiness, MIN_LRU_BATCH))
> - break;
> + orig_anon = nr[LRU_GEN_ANON];
> + orig_file = nr[LRU_GEN_FILE];
> + scanned = evict_folio_lists(nr, lruvec, sc, swappiness, MIN_LRU_BATCH);
>
> if (should_abort_scan(lruvec, sc))
> break;
> @@ -5252,6 +5264,21 @@ static bool try_to_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)
> if (root_reclaim(sc) && should_age)
> break;
>
> + /* Limit no_populatable aging to once per try_to_shrink_lruvec() */
> + no_populatable = allow_no_populatable_age &&
> + ((nr[LRU_GEN_ANON] && nr[LRU_GEN_ANON] == orig_anon) ||
> + (nr[LRU_GEN_FILE] && nr[LRU_GEN_FILE] == orig_file));
> + allow_no_populatable_age &= !no_populatable;
> + max_seq = READ_ONCE((lruvec)->lrugen.max_seq);
> + if (should_run_aging(lruvec, max_seq, sc, swappiness, no_populatable)) {
> + trace_mm_mglru_aging(lruvec, sc, swappiness);
> + if (try_to_inc_max_seq(lruvec, max_seq, swappiness, false))
> + need_rotate = true;
> + should_age = true;
> + }
[Severity: Medium]
Can reading lruvec->lrugen.max_seq after evict_folio_lists() cause
concurrent reclaimers to run redundant back-to-back aging passes on the
same lruvec?
try_to_inc_max_seq() relies on callers passing the max_seq value observed
when the need for aging was detected, deduplicating concurrent callers via
seq <= READ_ONCE(mm_state->seq). Because max_seq is now read after
evict_folio_lists(), two threads that both observe no_populatable while
max_seq is S can age the lruvec twice:
CPU0 CPU1
try_to_shrink_lruvec() try_to_shrink_lruvec()
evict_folio_lists() evict_folio_lists()
/* max_seq == S:
* anon scans 0, file shrinks
*/
/* max_seq == S:
* anon scans 0, file shrinks
*/
no_populatable = true;
max_seq = S;
try_to_inc_max_seq(..., S, ...)
/* advances max_seq to S + 1 */
no_populatable = true;
max_seq = READ_ONCE(lrugen.max_seq);
/* reads S + 1 */
should_run_aging(..., no_populatable)
/* returns true unconditionally */
try_to_inc_max_seq(..., S + 1, ...)
/* ages again to S + 2 */
<-- redundant aging
This triggers unnecessary page-table walks across mm_list, prematurely
advances min_seq once MAX_NR_GENS is reached, and decays PID controller
refault averages in reset_ctrl_pos() on each extra pass. Reading max_seq
before calling evict_folio_lists() preserves the sequence number at which
no_populatable was observed.
> + if (!scanned)
> + break;
[Severity: High]
Does checking !scanned after try_to_inc_max_seq() cause
try_to_shrink_lruvec() to abort without reclaiming any folios right after
aging succeeds?
In scan_folios(), the oldest generation considered for scanning depends on
reclaim priority:
mm/vmscan.c:scan_folios() {
...
if (sc->priority == DEF_PRIORITY)
seq = lrugen->max_seq - MIN_NR_GENS;
else if (sc->priority > DEF_PRIORITY / 2)
seq = lrugen->max_seq - 1;
else
seq = lrugen->max_seq;
seq = lruvec_populated_min_seq(lruvec, type, sc->reclaim_idx + 1, seq);
...
}
When sc->priority == DEF_PRIORITY and an lruvec has only MIN_NR_GENS (2)
generations (min_seq == max_seq - 1), scan_folios() sets seq to
max_seq - 2 (< min_seq) and evict_folio_lists() returns scanned == 0.
should_run_aging() and try_to_inc_max_seq() then advance max_seq to create
a third generation and set need_rotate = true, but if (!scanned) break
immediately exits the loop using the pre-aging scanned == 0 result,
rotating the memcg with zero folios reclaimed and escalating reclaim
priority prematurely.
The same premature exit happens at DEF_PRIORITY > sc->priority >
DEF_PRIORITY / 2 whenever the remaining type(s) with non-zero nr[] budget
only have folios in max_seq: evict_folio_lists() returns 0, no_populatable
triggers try_to_inc_max_seq() to move those folios into max_seq - 1, and
if (!scanned) break aborts the loop before they can be scanned, undoing
12316f7902f8 ("mm/mglru: don't abort scan immediately right after aging").
In addition, calling evict_folio_lists() and should_abort_scan() before
should_run_aging() causes scan_folios() at sc->priority <=
DEF_PRIORITY / 2 to evict directly from max_seq before
lru_gen_imbalanced() can age page tables to separate hot folios from cold
ones, and skips aging altogether when the first batch satisfies
should_abort_scan().
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113185541.70203-1-baohua@kernel.org?part=11
next prev parent reply other threads:[~2026-11-14 2:02 UTC|newest]
Thread overview: 21+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-13 18:55 [PATCH v8 00/16] mm: mglru: fix swappiness behavior Barry Song (Xiaomi)
2026-11-13 18:55 ` [PATCH v8 01/16] mm/mglru: report evictable sizes per type Barry Song (Xiaomi)
2026-11-13 18:55 ` [PATCH v8 02/16] mm/mglru: extract lruvec_populated_min_seq() helper Barry Song (Xiaomi)
2026-11-14 2:02 ` sashiko-bot
2026-11-13 18:55 ` [PATCH v8 03/16] mm/mglru: reclaim from the oldest populated gen Barry Song (Xiaomi)
2026-11-13 18:55 ` [PATCH v8 04/16] mm/mglru: lift the forced protection of reclaimer Barry Song (Xiaomi)
2026-11-13 18:55 ` [PATCH v8 05/16] mm/mglru: skip zones above reclaim_idx instead of rotating them Barry Song (Xiaomi)
2026-11-13 18:55 ` [PATCH v8 06/16] mm/mglru: return for unavailable anon reclaim earlier Barry Song (Xiaomi)
2026-11-13 18:55 ` [PATCH v8 07/16] mm/mglru: balance anon and file scan budget by refault rate Barry Song (Xiaomi)
2026-11-13 18:55 ` [PATCH v8 08/16] mm/mglru: lift youngest gen protection for higher priority Barry Song (Xiaomi)
2026-11-13 18:55 ` [PATCH v8 09/16] mm/mglru: Avoid placing rejected folios below populated gen Barry Song (Xiaomi)
2026-11-13 18:55 ` [PATCH v8 10/16] mm/mglru: Run aging when pages are rare outside the youngest gen Barry Song (Xiaomi)
2026-11-13 18:55 ` [PATCH v8 11/16] mm/mglru: Run aging when one type has no populatable folios Barry Song (Xiaomi)
2026-11-14 2:02 ` sashiko-bot [this message]
2026-11-13 18:55 ` [PATCH v8 12/16] mm/mglru: Restrict eviction accounting to eligible zones Barry Song (Xiaomi)
2026-11-14 2:02 ` sashiko-bot
2026-11-13 18:55 ` [PATCH v8 13/16] mm/mglru: Reduce LRU lock contention by shrinking folio batches in inc_min_seq Barry Song (Xiaomi)
2026-11-13 18:55 ` [PATCH v8 14/16] mm/mglru: Avoid prolonged anon reclaim with swappiness=200 Barry Song (Xiaomi)
2026-11-13 18:55 ` [PATCH v8 15/16] mm/mglru: Make swappiness=1 file reclaim more aggressive Barry Song (Xiaomi)
2026-11-13 18:55 ` [PATCH v8 16/16] mm/mglru: Avoid scan anon while swapcache is low for GFP_NOIO allocation Barry Song (Xiaomi)
2026-11-13 19:50 ` [PATCH v8 00/16] mm: mglru: fix swappiness behavior Andrew Morton
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=sashiko-outbox-214030@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=akpm@linux-foundation.org \
--cc=axelrasmussen@google.com \
--cc=baohua@kernel.org \
--cc=baolin.wang@linux.alibaba.com \
--cc=baoquan.he@linux.dev \
--cc=hannes@cmpxchg.org \
--cc=hui.zhu@linux.dev \
--cc=kasong@tencent.com \
--cc=kunwu.chan@gmail.com \
--cc=liam@infradead.org \
--cc=lianux.mm@gmail.com \
--cc=linux-mm@kvack.org \
--cc=liuxiang.277@bytedance.com \
--cc=lyugaofei@xiaomi.com \
--cc=mhocko@kernel.org \
--cc=mhocko@suse.com \
--cc=qi.zheng@linux.dev \
--cc=ridong.chen@linux.dev \
--cc=rppt@kernel.org \
--cc=ryncsn@gmail.com \
--cc=sashiko-reviews@lists.linux.dev \
--cc=shakeel.butt@linux.dev \
--cc=sidchintamaneni@gmail.com \
--cc=surenb@google.com \
--cc=wangzicheng@honor.com \
--cc=weixugc@google.com \
--cc=yuanchu@google.com \
--cc=zhangbo56@xiaomi.com \
--cc=zhuhui@kylinos.cn \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox