Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH] mm/mglru: fix ineffective memory protection for non-kswapd reclaim
@ 2026-08-26 13:30 Ridong Chen
  2026-08-27  1:37 ` Ridong Chen
                   ` (2 more replies)
  0 siblings, 3 replies; 6+ messages in thread
From: Ridong Chen @ 2026-08-26 13:30 UTC (permalink / raw)
  To: Johannes Weiner, Michal Hocko, Roman Gushchin, Shakeel Butt,
	Andrew Morton
  Cc: Muchun Song, David Hildenbrand, Qi Zheng, Lorenzo Stoakes,
	Kairui Song, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu,
	Yu Zhao,
	open list:CONTROL GROUP - MEMORY RESOURCE CONTROLLER (MEMCG),
	open list:CONTROL GROUP - MEMORY RESOURCE CONTROLLER (MEMCG),
	linux-kernel, Ridong Chen, Ridong Chen, stable

From: Ridong Chen <chenridong@xiaomi.com>

memory.min/low is silently bypassed for MGLRU during global proactive
reclaim (writing to the root memory.reclaim) and global direct 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 path is not affected because
shrink_node() calls mem_cgroup_calculate_protection() for each memcg it
visits during a top-down tree walk.

Commit 30d77b7eef01 ("mm/mglru: fix ineffective protection calculation")
moved the protection computation into lru_gen_age_node(), which only
runs for kswapd.  Non-kswapd global reclaim reaches shrink_one() through
lru_gen_shrink_node() -> shrink_many() without any protection
computation, so emin/elow remain stale or zero.

Introduce mem_cgroup_protection_path() which computes emin/elow along
the root-to-target path only by iterating through the cgroup ancestors
array top-down.  This avoids the full tree traversal that would be
needed with mem_cgroup_calculate_protection(), limiting the cost to
O(depth) per memcg - typically 3-5 levels.

Call it from shrink_one() for the non-kswapd path so that each memcg
about to be shrunk has correct protection values.

Fixes: e4dde56cd208 ("mm: multi-gen LRU: per-node lru_gen_folio lists")
Cc: stable@vger.kernel.org
Signed-off-by: Ridong Chen <chenridong@xiaomi.com>
---
 include/linux/memcontrol.h |  7 +++++++
 mm/memcontrol.c            | 42 ++++++++++++++++++++++++++++++++++++++
 mm/vmscan.c                |  8 +++++++-
 3 files changed, 56 insertions(+), 1 deletion(-)

diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h
index 7d1c0ce189a8..26be8277d64b 100644
--- a/include/linux/memcontrol.h
+++ b/include/linux/memcontrol.h
@@ -605,6 +605,8 @@ static inline void mem_cgroup_protection(struct mem_cgroup *root,
 
 void mem_cgroup_calculate_protection(struct mem_cgroup *root,
 				     struct mem_cgroup *memcg);
+void mem_cgroup_protection_path(struct mem_cgroup *root,
+				struct mem_cgroup *memcg);
 
 static inline bool mem_cgroup_unprotected(struct mem_cgroup *target,
 					  struct mem_cgroup *memcg)
@@ -1133,6 +1135,11 @@ static inline void mem_cgroup_calculate_protection(struct mem_cgroup *root,
 {
 }
 
+static inline void mem_cgroup_protection_path(struct mem_cgroup *root,
+					      struct mem_cgroup *memcg)
+{
+}
+
 static inline bool mem_cgroup_unprotected(struct mem_cgroup *target,
 					  struct mem_cgroup *memcg)
 {
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index 1271d390b617..c739db33b917 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -5198,6 +5198,48 @@ void mem_cgroup_calculate_protection(struct mem_cgroup *root,
 	page_counter_calculate_protection(&root->memory, &memcg->memory, recursive_protection);
 }
 
+/**
+ * mem_cgroup_protection_path - compute protection along root->memcg path
+ * @root: the top ancestor of the sub-tree being checked (NULL for root_mem_cgroup)
+ * @memcg: the target memory cgroup
+ *
+ * Walk the ancestor path from @root down to @memcg and compute the effective
+ * protection at each level.  This is safe for isolated queries because it
+ * ensures parents are computed before children.
+ */
+void mem_cgroup_protection_path(struct mem_cgroup *root,
+				struct mem_cgroup *memcg)
+{
+	bool recursive_protection =
+		cgrp_dfl_root.flags & CGRP_ROOT_MEMORY_RECURSIVE_PROT;
+	struct cgroup *cg;
+	int root_level, i;
+
+	if (mem_cgroup_disabled())
+		return;
+
+	if (!root)
+		root = root_mem_cgroup;
+
+	if (memcg == root)
+		return;
+
+	root_level = root->css.cgroup->level;
+	cg = memcg->css.cgroup;
+
+	rcu_read_lock();
+	for (i = root_level + 1; i <= cg->level; i++) {
+		struct mem_cgroup *cur;
+
+		cur = mem_cgroup_from_css(cgroup_css(cg->ancestors[i],
+						    &memory_cgrp_subsys));
+		if (cur)
+			page_counter_calculate_protection(&root->memory,
+					&cur->memory, recursive_protection);
+	}
+	rcu_read_unlock();
+}
+
 static int charge_memcg(struct folio *folio, struct mem_cgroup *memcg,
 			gfp_t gfp)
 {
diff --git a/mm/vmscan.c b/mm/vmscan.c
index 6dff207ad8c6..e572d2742c8c 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -5102,7 +5102,13 @@ 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() */
+	/*
+	 * For kswapd, lru_gen_age_node() has already called
+	 * mem_cgroup_calculate_protection()
+	 */
+	if (!current_is_kswapd())
+		mem_cgroup_protection_path(NULL, memcg);
+
 	if (mem_cgroup_below_min(NULL, memcg))
 		return MEMCG_LRU_YOUNG;
 
-- 
2.34.1



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

* Re: [RFC PATCH] mm/mglru: fix ineffective memory protection for non-kswapd reclaim
  2026-08-26 13:30 [RFC PATCH] mm/mglru: fix ineffective memory protection for non-kswapd reclaim Ridong Chen
@ 2026-08-27  1:37 ` Ridong Chen
       [not found] ` <20260826134733.051941F000E9@smtp.kernel.org>
  2026-08-27 17:21 ` Johannes Weiner
  2 siblings, 0 replies; 6+ messages in thread
From: Ridong Chen @ 2026-08-27  1:37 UTC (permalink / raw)
  To: Johannes Weiner, Michal Hocko, Roman Gushchin, Shakeel Butt,
	Andrew Morton
  Cc: Muchun Song, David Hildenbrand, Qi Zheng, Lorenzo Stoakes,
	Kairui Song, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu,
	Yu Zhao,
	open list:CONTROL GROUP - MEMORY RESOURCE CONTROLLER (MEMCG),
	open list:CONTROL GROUP - MEMORY RESOURCE CONTROLLER (MEMCG),
	linux-kernel, Ridong Chen, stable



On 8/26/2026 9:30 PM, Ridong Chen wrote:
> From: Ridong Chen <chenridong@xiaomi.com>
> 
> memory.min/low is silently bypassed for MGLRU during global proactive
> reclaim (writing to the root memory.reclaim) and global direct 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 path is not affected because
> shrink_node() calls mem_cgroup_calculate_protection() for each memcg it
> visits during a top-down tree walk.
> 
> Commit 30d77b7eef01 ("mm/mglru: fix ineffective protection calculation")
> moved the protection computation into lru_gen_age_node(), which only
> runs for kswapd.  Non-kswapd global reclaim reaches shrink_one() through
> lru_gen_shrink_node() -> shrink_many() without any protection
> computation, so emin/elow remain stale or zero.
> 
> Introduce mem_cgroup_protection_path() which computes emin/elow along
> the root-to-target path only by iterating through the cgroup ancestors
> array top-down.  This avoids the full tree traversal that would be
> needed with mem_cgroup_calculate_protection(), limiting the cost to
> O(depth) per memcg - typically 3-5 levels.
> 

Hi all,

This is an RFC patch.

My initial attempt to address this issue involved traversing the entire memcg 
tree, similar to what kswapd does. However, Sashiko raised concerns that this 
could introduce performance regressions [1].

To address that, this patch instead walks the cgroup ancestors array in a 
top-down manner, as outlined above, and I'd like to gather feedback on this 
approach.

[1] https://lore.kernel.org/linux-mm/20260723130559.2343690-1-ridong.chen@linux.dev/

> Call it from shrink_one() for the non-kswapd path so that each memcg
> about to be shrunk has correct protection values.
> 
> Fixes: e4dde56cd208 ("mm: multi-gen LRU: per-node lru_gen_folio lists")
> Cc: stable@vger.kernel.org
> Signed-off-by: Ridong Chen <chenridong@xiaomi.com>
> ---
>   include/linux/memcontrol.h |  7 +++++++
>   mm/memcontrol.c            | 42 ++++++++++++++++++++++++++++++++++++++
>   mm/vmscan.c                |  8 +++++++-
>   3 files changed, 56 insertions(+), 1 deletion(-)
> 
> diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h
> index 7d1c0ce189a8..26be8277d64b 100644
> --- a/include/linux/memcontrol.h
> +++ b/include/linux/memcontrol.h
> @@ -605,6 +605,8 @@ static inline void mem_cgroup_protection(struct mem_cgroup *root,
>   
>   void mem_cgroup_calculate_protection(struct mem_cgroup *root,
>   				     struct mem_cgroup *memcg);
> +void mem_cgroup_protection_path(struct mem_cgroup *root,
> +				struct mem_cgroup *memcg);
>   
>   static inline bool mem_cgroup_unprotected(struct mem_cgroup *target,
>   					  struct mem_cgroup *memcg)
> @@ -1133,6 +1135,11 @@ static inline void mem_cgroup_calculate_protection(struct mem_cgroup *root,
>   {
>   }
>   
> +static inline void mem_cgroup_protection_path(struct mem_cgroup *root,
> +					      struct mem_cgroup *memcg)
> +{
> +}
> +
>   static inline bool mem_cgroup_unprotected(struct mem_cgroup *target,
>   					  struct mem_cgroup *memcg)
>   {
> diff --git a/mm/memcontrol.c b/mm/memcontrol.c
> index 1271d390b617..c739db33b917 100644
> --- a/mm/memcontrol.c
> +++ b/mm/memcontrol.c
> @@ -5198,6 +5198,48 @@ void mem_cgroup_calculate_protection(struct mem_cgroup *root,
>   	page_counter_calculate_protection(&root->memory, &memcg->memory, recursive_protection);
>   }
>   
> +/**
> + * mem_cgroup_protection_path - compute protection along root->memcg path
> + * @root: the top ancestor of the sub-tree being checked (NULL for root_mem_cgroup)
> + * @memcg: the target memory cgroup
> + *
> + * Walk the ancestor path from @root down to @memcg and compute the effective
> + * protection at each level.  This is safe for isolated queries because it
> + * ensures parents are computed before children.
> + */
> +void mem_cgroup_protection_path(struct mem_cgroup *root,
> +				struct mem_cgroup *memcg)
> +{
> +	bool recursive_protection =
> +		cgrp_dfl_root.flags & CGRP_ROOT_MEMORY_RECURSIVE_PROT;
> +	struct cgroup *cg;
> +	int root_level, i;
> +
> +	if (mem_cgroup_disabled())
> +		return;
> +
> +	if (!root)
> +		root = root_mem_cgroup;
> +
> +	if (memcg == root)
> +		return;
> +
> +	root_level = root->css.cgroup->level;
> +	cg = memcg->css.cgroup;
> +
> +	rcu_read_lock();
> +	for (i = root_level + 1; i <= cg->level; i++) {
> +		struct mem_cgroup *cur;
> +
> +		cur = mem_cgroup_from_css(cgroup_css(cg->ancestors[i],
> +						    &memory_cgrp_subsys));
> +		if (cur)
> +			page_counter_calculate_protection(&root->memory,
> +					&cur->memory, recursive_protection);
> +	}
> +	rcu_read_unlock();
> +}
> +
>   static int charge_memcg(struct folio *folio, struct mem_cgroup *memcg,
>   			gfp_t gfp)
>   {
> diff --git a/mm/vmscan.c b/mm/vmscan.c
> index 6dff207ad8c6..e572d2742c8c 100644
> --- a/mm/vmscan.c
> +++ b/mm/vmscan.c
> @@ -5102,7 +5102,13 @@ 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() */
> +	/*
> +	 * For kswapd, lru_gen_age_node() has already called
> +	 * mem_cgroup_calculate_protection()
> +	 */
> +	if (!current_is_kswapd())
> +		mem_cgroup_protection_path(NULL, memcg);
> +
>   	if (mem_cgroup_below_min(NULL, memcg))
>   		return MEMCG_LRU_YOUNG;
>   

-- 
Best regards
Ridong



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

* Re: [RFC PATCH] mm/mglru: fix ineffective memory protection for non-kswapd reclaim
       [not found]   ` <0c606cf9-2b5a-4549-8ab1-0be619487412@linux.dev>
@ 2026-08-27  9:26     ` Michal Koutný
  2026-08-28  1:49       ` Ridong Chen
  0 siblings, 1 reply; 6+ messages in thread
From: Michal Koutný @ 2026-08-27  9:26 UTC (permalink / raw)
  To: Ridong Chen
  Cc: sashiko-reviews, tj, hannes,
	open list:CONTROL GROUP - MEMORY RESOURCE CONTROLLER (MEMCG)

[-- Attachment #1: Type: text/plain, Size: 1232 bytes --]

Hi Ridong.

On Thu, Aug 27, 2026 at 09:29:13AM +0800, Ridong Chen <ridong.chen@linux.dev> wrote:
> On 8/26/2026 9:47 PM, sashiko-bot@kernel.org wrote:
> > Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> > 
> > Pre-existing issues:
> > - [High] Integer overflow in `effective_protection()` calculations on systems with >= 16TB RAM.
> > --
> > 
> 
> Thanks for the careful analysis. You're right that this is pre-existing in
> effective_protection() and not introduced by this patch.
> 
> I'm not sure it's worth fixing in practice, though — the overflow requires
> systems with >= 16TB of RAM, which I've never encountered
> myself. So I'd rather not fold a fix for it into this patch.
> 
> I'd like to hear the maintainers' thoughts on whether this is worth
> addressing separately.

Machines with >=16 TiB aren't completely out of reach [1]. Then
configuring such protections to attain the overflow is another
condition, so I'd consider this a nice to have but not a
pressing fixup (separately, with thorough review of occurences where
total page counts/bytes might get multiplied).

HTH,
Michal

[1] E.g. https://aws.amazon.com/ec2/instance-types/u7i/

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 265 bytes --]

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

* Re: [RFC PATCH] mm/mglru: fix ineffective memory protection for non-kswapd reclaim
  2026-08-26 13:30 [RFC PATCH] mm/mglru: fix ineffective memory protection for non-kswapd reclaim Ridong Chen
  2026-08-27  1:37 ` Ridong Chen
       [not found] ` <20260826134733.051941F000E9@smtp.kernel.org>
@ 2026-08-27 17:21 ` Johannes Weiner
  2026-08-28  1:51   ` Ridong Chen
  2 siblings, 1 reply; 6+ messages in thread
From: Johannes Weiner @ 2026-08-27 17:21 UTC (permalink / raw)
  To: Ridong Chen
  Cc: Michal Hocko, Roman Gushchin, Shakeel Butt, Andrew Morton,
	Muchun Song, David Hildenbrand, Qi Zheng, Lorenzo Stoakes,
	Kairui Song, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu,
	Yu Zhao,
	open list:CONTROL GROUP - MEMORY RESOURCE CONTROLLER (MEMCG),
	open list:CONTROL GROUP - MEMORY RESOURCE CONTROLLER (MEMCG),
	linux-kernel, Ridong Chen, stable

On Wed, Aug 26, 2026 at 09:30:54PM +0800, Ridong Chen wrote:
> From: Ridong Chen <chenridong@xiaomi.com>
> 
> memory.min/low is silently bypassed for MGLRU during global proactive
> reclaim (writing to the root memory.reclaim) and global direct 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 path is not affected because
> shrink_node() calls mem_cgroup_calculate_protection() for each memcg it
> visits during a top-down tree walk.
> 
> Commit 30d77b7eef01 ("mm/mglru: fix ineffective protection calculation")
> moved the protection computation into lru_gen_age_node(), which only
> runs for kswapd.  Non-kswapd global reclaim reaches shrink_one() through
> lru_gen_shrink_node() -> shrink_many() without any protection
> computation, so emin/elow remain stale or zero.
> 
> Introduce mem_cgroup_protection_path() which computes emin/elow along
> the root-to-target path only by iterating through the cgroup ancestors
> array top-down.  This avoids the full tree traversal that would be
> needed with mem_cgroup_calculate_protection(), limiting the cost to
> O(depth) per memcg - typically 3-5 levels.

Right, because of the memcg-lru...

> --- a/mm/memcontrol.c
> +++ b/mm/memcontrol.c
> @@ -5198,6 +5198,48 @@ void mem_cgroup_calculate_protection(struct mem_cgroup *root,
>  	page_counter_calculate_protection(&root->memory, &memcg->memory, recursive_protection);
>  }
>  
> +/**
> + * mem_cgroup_protection_path - compute protection along root->memcg path
> + * @root: the top ancestor of the sub-tree being checked (NULL for root_mem_cgroup)
> + * @memcg: the target memory cgroup
> + *
> + * Walk the ancestor path from @root down to @memcg and compute the effective
> + * protection at each level.  This is safe for isolated queries because it
> + * ensures parents are computed before children.
> + */
> +void mem_cgroup_protection_path(struct mem_cgroup *root,
> +				struct mem_cgroup *memcg)
> +{
> +	bool recursive_protection =
> +		cgrp_dfl_root.flags & CGRP_ROOT_MEMORY_RECURSIVE_PROT;
> +	struct cgroup *cg;
> +	int root_level, i;
> +
> +	if (mem_cgroup_disabled())
> +		return;
> +
> +	if (!root)
> +		root = root_mem_cgroup;
> +
> +	if (memcg == root)
> +		return;
> +
> +	root_level = root->css.cgroup->level;
> +	cg = memcg->css.cgroup;
> +
> +	rcu_read_lock();
> +	for (i = root_level + 1; i <= cg->level; i++) {
> +		struct mem_cgroup *cur;
> +
> +		cur = mem_cgroup_from_css(cgroup_css(cg->ancestors[i],
> +						    &memory_cgrp_subsys));
> +		if (cur)
> +			page_counter_calculate_protection(&root->memory,
> +					&cur->memory, recursive_protection);
> +	}
> +	rcu_read_unlock();
> +}

Please wrap this in a #ifdef CONFIG_LRU_GEN block.


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

* Re: [RFC PATCH] mm/mglru: fix ineffective memory protection for non-kswapd reclaim
  2026-08-27  9:26     ` Michal Koutný
@ 2026-08-28  1:49       ` Ridong Chen
  0 siblings, 0 replies; 6+ messages in thread
From: Ridong Chen @ 2026-08-28  1:49 UTC (permalink / raw)
  To: Michal Koutný
  Cc: sashiko-reviews, tj, hannes,
	open list:CONTROL GROUP - MEMORY RESOURCE CONTROLLER (MEMCG)



On 8/27/2026 5:26 PM, Michal Koutný wrote:
> Hi Ridong.
> 
> On Thu, Aug 27, 2026 at 09:29:13AM +0800, Ridong Chen <ridong.chen@linux.dev> wrote:
>> On 8/26/2026 9:47 PM, sashiko-bot@kernel.org wrote:
>>> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
>>>
>>> Pre-existing issues:
>>> - [High] Integer overflow in `effective_protection()` calculations on systems with >= 16TB RAM.
>>> --
>>>
>>
>> Thanks for the careful analysis. You're right that this is pre-existing in
>> effective_protection() and not introduced by this patch.
>>
>> I'm not sure it's worth fixing in practice, though — the overflow requires
>> systems with >= 16TB of RAM, which I've never encountered
>> myself. So I'd rather not fold a fix for it into this patch.
>>
>> I'd like to hear the maintainers' thoughts on whether this is worth
>> addressing separately.
> 
> Machines with >=16 TiB aren't completely out of reach [1]. Then
> configuring such protections to attain the overflow is another
> condition, so I'd consider this a nice to have but not a
> pressing fixup (separately, with thorough review of occurences where
> total page counts/bytes might get multiplied).
> 
> HTH,
> Michal
> 
> [1] E.g. https://aws.amazon.com/ec2/instance-types/u7i/

Thanks Michal,

It's worth fixing now, I'll send a separate patch to address it.

-- 
Best regards
Ridong



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

* Re: [RFC PATCH] mm/mglru: fix ineffective memory protection for non-kswapd reclaim
  2026-08-27 17:21 ` Johannes Weiner
@ 2026-08-28  1:51   ` Ridong Chen
  0 siblings, 0 replies; 6+ messages in thread
From: Ridong Chen @ 2026-08-28  1:51 UTC (permalink / raw)
  To: Johannes Weiner
  Cc: Michal Hocko, Roman Gushchin, Shakeel Butt, Andrew Morton,
	Muchun Song, David Hildenbrand, Qi Zheng, Lorenzo Stoakes,
	Kairui Song, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu,
	Yu Zhao,
	open list:CONTROL GROUP - MEMORY RESOURCE CONTROLLER (MEMCG),
	open list:CONTROL GROUP - MEMORY RESOURCE CONTROLLER (MEMCG),
	linux-kernel, Ridong Chen, stable



On 8/28/2026 1:21 AM, Johannes Weiner wrote:
> On Wed, Aug 26, 2026 at 09:30:54PM +0800, Ridong Chen wrote:
>> From: Ridong Chen <chenridong@xiaomi.com>
>>
>> memory.min/low is silently bypassed for MGLRU during global proactive
>> reclaim (writing to the root memory.reclaim) and global direct 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 path is not affected because
>> shrink_node() calls mem_cgroup_calculate_protection() for each memcg it
>> visits during a top-down tree walk.
>>
>> Commit 30d77b7eef01 ("mm/mglru: fix ineffective protection calculation")
>> moved the protection computation into lru_gen_age_node(), which only
>> runs for kswapd.  Non-kswapd global reclaim reaches shrink_one() through
>> lru_gen_shrink_node() -> shrink_many() without any protection
>> computation, so emin/elow remain stale or zero.
>>
>> Introduce mem_cgroup_protection_path() which computes emin/elow along
>> the root-to-target path only by iterating through the cgroup ancestors
>> array top-down.  This avoids the full tree traversal that would be
>> needed with mem_cgroup_calculate_protection(), limiting the cost to
>> O(depth) per memcg - typically 3-5 levels.
> 
> Right, because of the memcg-lru...
> 

Yep, that's it.

>> --- a/mm/memcontrol.c
>> +++ b/mm/memcontrol.c
>> @@ -5198,6 +5198,48 @@ void mem_cgroup_calculate_protection(struct mem_cgroup *root,
>>   	page_counter_calculate_protection(&root->memory, &memcg->memory, recursive_protection);
>>   }
>>   
>> +/**
>> + * mem_cgroup_protection_path - compute protection along root->memcg path
>> + * @root: the top ancestor of the sub-tree being checked (NULL for root_mem_cgroup)
>> + * @memcg: the target memory cgroup
>> + *
>> + * Walk the ancestor path from @root down to @memcg and compute the effective
>> + * protection at each level.  This is safe for isolated queries because it
>> + * ensures parents are computed before children.
>> + */
>> +void mem_cgroup_protection_path(struct mem_cgroup *root,
>> +				struct mem_cgroup *memcg)
>> +{
>> +	bool recursive_protection =
>> +		cgrp_dfl_root.flags & CGRP_ROOT_MEMORY_RECURSIVE_PROT;
>> +	struct cgroup *cg;
>> +	int root_level, i;
>> +
>> +	if (mem_cgroup_disabled())
>> +		return;
>> +
>> +	if (!root)
>> +		root = root_mem_cgroup;
>> +
>> +	if (memcg == root)
>> +		return;
>> +
>> +	root_level = root->css.cgroup->level;
>> +	cg = memcg->css.cgroup;
>> +
>> +	rcu_read_lock();
>> +	for (i = root_level + 1; i <= cg->level; i++) {
>> +		struct mem_cgroup *cur;
>> +
>> +		cur = mem_cgroup_from_css(cgroup_css(cg->ancestors[i],
>> +						    &memory_cgrp_subsys));
>> +		if (cur)
>> +			page_counter_calculate_protection(&root->memory,
>> +					&cur->memory, recursive_protection);
>> +	}
>> +	rcu_read_unlock();
>> +}
> 
> Please wrap this in a #ifdef CONFIG_LRU_GEN block.

Will add.

-- 
Best regards
Ridong



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

end of thread, other threads:[~2026-08-28  1:52 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-26 13:30 [RFC PATCH] mm/mglru: fix ineffective memory protection for non-kswapd reclaim Ridong Chen
2026-08-27  1:37 ` Ridong Chen
     [not found] ` <20260826134733.051941F000E9@smtp.kernel.org>
     [not found]   ` <0c606cf9-2b5a-4549-8ab1-0be619487412@linux.dev>
2026-08-27  9:26     ` Michal Koutný
2026-08-28  1:49       ` Ridong Chen
2026-08-27 17:21 ` Johannes Weiner
2026-08-28  1:51   ` Ridong Chen

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox