Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
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


  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