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: 22+ 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-14 2:27 ` Barry Song
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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.