All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] mm/mglru: fix memcg protection for global proactive reclaim
@ 2026-07-23 13:05 Ridong
  2026-07-23 23:58 ` Andrew Morton
  0 siblings, 1 reply; 3+ messages in thread
From: Ridong @ 2026-07-23 13:05 UTC (permalink / raw)
  To: Andrew Morton, Johannes Weiner
  Cc: David Hildenbrand, Michal Hocko, Qi Zheng, Shakeel Butt,
	Lorenzo Stoakes, Kairui Song, Barry Song, Axel Rasmussen,
	Yuanchu Xie, Wei Xu, Yu Zhao, linux-mm, linux-kernel, Ridong Chen,
	Ridong Chen

From: Ridong Chen <chenridong@xiaomi.com>

memory.min/low is silently bypassed for MGLRU during global proactive
reclaim (writing to the root memory.reclaim). It can be reproduced as
follows:

  # echo 7 > /sys/kernel/mm/lru_gen/enabled
  # cd /sys/fs/cgroup
  # mkdir -p a/b
  # echo 100M > a/memory.min
  # echo +memory > a/cgroup.subtree_control
  # echo 100M > a/b/memory.min
  # echo $$ > a/b/cgroup.procs
  # dd if=/dev/zero of=/tmp/testfile bs=1M count=200
  # cat a/b/memory.current
  222650368
  # echo 500M > memory.reclaim
  -bash: echo: write error: Resource temporarily unavailable
  # cat a/b/memory.current
  6070272

memory.min is 100M, yet reclaim drops a/b down to 6M, breaking the
protection. The traditional LRU is not affected.

Commit 30d77b7eef01 ("mm/mglru: fix ineffective protection calculation")
computes the memcg protection in lru_gen_age_node(), which only runs for
kswapd. Global proactive reclaim reaches shrink_one() through
lru_gen_shrink_node() without any aging step, so emin/elow is not
couputed,

Factor the tree traversal out into update_memcg_protection() and call
it from lru_gen_shrink_node() for the non-kswapd path, so the protection
is computed before shrinking. kswapd keeps computing it in
lru_gen_age_node(), which also needs it for the min_ttl OOM check.

Fixes: e4dde56cd208 ("mm: multi-gen LRU: per-node lru_gen_folio lists")
Signed-off-by: Ridong Chen <chenridong@xiaomi.com>
---
 mm/vmscan.c | 33 +++++++++++++++++++++++++--------
 1 file changed, 25 insertions(+), 8 deletions(-)

diff --git a/mm/vmscan.c b/mm/vmscan.c
index 06e103f9781e..dc3549583e97 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -4122,6 +4122,16 @@ static bool lruvec_is_reclaimable(struct lruvec *lruvec, struct scan_control *sc
 	return time_is_before_jiffies(birth + min_ttl);
 }
 
+static void update_memcg_protection(void)
+{
+	struct mem_cgroup *memcg;
+
+	memcg = mem_cgroup_iter(NULL, NULL, NULL);
+	do {
+		mem_cgroup_calculate_protection(NULL, memcg);
+	} while ((memcg = mem_cgroup_iter(NULL, memcg, NULL)));
+}
+
 /* to protect the working set of the last N jiffies */
 static unsigned long lru_gen_min_ttl __read_mostly;
 
@@ -4135,15 +4145,19 @@ static void lru_gen_age_node(struct pglist_data *pgdat, struct scan_control *sc)
 
 	set_initial_priority(pgdat, sc);
 
-	memcg = mem_cgroup_iter(NULL, NULL, NULL);
-	do {
-		struct lruvec *lruvec = mem_cgroup_lruvec(memcg, pgdat);
+	update_memcg_protection();
 
-		mem_cgroup_calculate_protection(NULL, memcg);
+	if (min_ttl) {
+		memcg = mem_cgroup_iter(NULL, NULL, NULL);
+		do {
+			struct lruvec *lruvec = mem_cgroup_lruvec(memcg, pgdat);
 
-		if (!reclaimable)
-			reclaimable = lruvec_is_reclaimable(lruvec, sc, min_ttl);
-	} while ((memcg = mem_cgroup_iter(NULL, memcg, NULL)));
+			if (lruvec_is_reclaimable(lruvec, sc, min_ttl)) {
+				reclaimable = true;
+				break;
+			}
+		} while ((memcg = mem_cgroup_iter(NULL, memcg, NULL)));
+	}
 
 	/*
 	 * The main goal is to OOM kill if every generation from all memcgs is
@@ -5004,7 +5018,7 @@ static int shrink_one(struct lruvec *lruvec, struct scan_control *sc)
 	struct mem_cgroup *memcg = lruvec_memcg(lruvec);
 	struct pglist_data *pgdat = lruvec_pgdat(lruvec);
 
-	/* lru_gen_age_node() called mem_cgroup_calculate_protection() */
+	/* update_memcg_protection() computed the protection */
 	if (mem_cgroup_below_min(NULL, memcg))
 		return MEMCG_LRU_YOUNG;
 
@@ -5151,8 +5165,11 @@ static void lru_gen_shrink_node(struct pglist_data *pgdat, struct scan_control *
 
 	set_initial_priority(pgdat, sc);
 
+	/* kswapd called update_memcg_protection in lru_gen_age_node */
 	if (current_is_kswapd())
 		sc->nr_reclaimed = 0;
+	else
+		update_memcg_protection();
 
 	if (mem_cgroup_disabled())
 		shrink_one(&pgdat->__lruvec, sc);
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: [PATCH] mm/mglru: fix memcg protection for global proactive reclaim
  2026-07-23 13:05 [PATCH] mm/mglru: fix memcg protection for global proactive reclaim Ridong
@ 2026-07-23 23:58 ` Andrew Morton
  2026-07-24  3:47   ` Ridong Chen
  0 siblings, 1 reply; 3+ messages in thread
From: Andrew Morton @ 2026-07-23 23:58 UTC (permalink / raw)
  To: Ridong
  Cc: Johannes Weiner, David Hildenbrand, Michal Hocko, Qi Zheng,
	Shakeel Butt, Lorenzo Stoakes, Kairui Song, Barry Song,
	Axel Rasmussen, Yuanchu Xie, Wei Xu, Yu Zhao, linux-mm,
	linux-kernel, Ridong Chen

On Thu, 23 Jul 2026 21:05:59 +0800 Ridong <ridong.chen@linux.dev> wrote:

> memory.min/low is silently bypassed for MGLRU during global proactive
> reclaim (writing to the root memory.reclaim). It can be reproduced as
> follows:
> 
> ...
> 
> Factor the tree traversal out into update_memcg_protection() and call
> it from lru_gen_shrink_node() for the non-kswapd path, so the protection
> is computed before shrinking. kswapd keeps computing it in
> lru_gen_age_node(), which also needs it for the min_ttl OOM check.
> 
> Fixes: e4dde56cd208 ("mm: multi-gen LRU: per-node lru_gen_folio lists")

Do we want cc:stable on this fix?

Sashiko said a couple of things - the memcg ref leak looks real:
	https://sashiko.dev/#/patchset/20260723130559.2343690-1-ridong.chen@linux.dev


^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH] mm/mglru: fix memcg protection for global proactive reclaim
  2026-07-23 23:58 ` Andrew Morton
@ 2026-07-24  3:47   ` Ridong Chen
  0 siblings, 0 replies; 3+ messages in thread
From: Ridong Chen @ 2026-07-24  3:47 UTC (permalink / raw)
  To: Andrew Morton
  Cc: Johannes Weiner, David Hildenbrand, Michal Hocko, Qi Zheng,
	Shakeel Butt, Lorenzo Stoakes, Kairui Song, Barry Song,
	Axel Rasmussen, Yuanchu Xie, Wei Xu, Yu Zhao, linux-mm,
	linux-kernel, Ridong Chen



On 7/24/2026 7:58 AM, Andrew Morton wrote:
> On Thu, 23 Jul 2026 21:05:59 +0800 Ridong <ridong.chen@linux.dev> wrote:
> 
>> memory.min/low is silently bypassed for MGLRU during global proactive
>> reclaim (writing to the root memory.reclaim). It can be reproduced as
>> follows:
>>
>> ...
>>
>> Factor the tree traversal out into update_memcg_protection() and call
>> it from lru_gen_shrink_node() for the non-kswapd path, so the protection
>> is computed before shrinking. kswapd keeps computing it in
>> lru_gen_age_node(), which also needs it for the min_ttl OOM check.
>>
>> Fixes: e4dde56cd208 ("mm: multi-gen LRU: per-node lru_gen_folio lists")
> 
> Do we want cc:stable on this fix?
> 
> Sashiko said a couple of things - the memcg ref leak looks real:
> 	https://sashiko.dev/#/patchset/20260723130559.2343690-1-ridong.chen@linux.dev
> 

Sashiko said:

   When breaking out of the loop early here, do we need to call
   mem_cgroup_iter_break(NULL, memcg) to release the reference?
   Since mem_cgroup_iter() holds a reference to the active cgroup css, exiting
   without dropping it could cause memory cgroups to leak and accumulate over
   time, eventually leading to kernel memory exhaustion.
   [ ... ]

This is a bug introduced by this patch, and we will fix it.

Regarding the performance regression:

   Placing update_memcg_protection() inside the lru_gen_shrink_node()
   non-kswapd path forces every direct reclaimer into an unbounded full
   cgroup tree walk.
   Will this cause severe performance regressions during global memory pressure?
   All allocating tasks entering global direct reclaim would concurrently
   traverse the entire memcg tree. This could lead to massive css->refcnt
   cacheline bouncing and system latency spikes, scaling negatively with
   the number of memory cgroups.
   Could this full tree walk be optimized or deferred so direct reclaimers avoid
   iterating every single cgroup?

Since MGLRU global reclaim does not iterate over memcgs in the same way 
traditional LRU does (which traverses the hierarchy from top to bottom), it 
appears we are currently required to walk the full tree, similar to what kswapd 
reclaim does.

Does anyone have a better approach in mind?

-- 
Best regards
Ridong



^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-07-24  3:47 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-23 13:05 [PATCH] mm/mglru: fix memcg protection for global proactive reclaim Ridong
2026-07-23 23:58 ` Andrew Morton
2026-07-24  3:47   ` Ridong Chen

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.