make unused huge shrinker memcg aware
Subsystems (6)
v4 cover make unused huge shrinker memcg aware
From: Qi Zheng <[email protected]>
Changes in v4:
- add [PATCH v4 2/4] to make obj_cgroup_memcg() handle NULL objcg
- store obj_cgroup instead of mem_cgroup in shmem_inode_info to avoid pinning a dying memcg through a long-lived CSS reference (pointed by sashiko)
- fix is_shmem_unused_huge_match() to always check the NUMA node for shrinker reclaim, not only for non-root memcg reclaim (pointed by sashiko)
- collect Reviewed-by
- rebase onto the next-20260814
Note: [PATCH v4 1/4] should ideally be folded into commit 0ef8faff490be ("fs: push nr_cached_objects memcg gating into individual filesystems") in linux-next.
Changes in v3:
- add a fix patch to fix missed removal of super_fs_objects_eligible()
- move the original shrinklist addition logic after all checks are completed, and split it into a separate patch. (suggested by Baolin)
- simplify the shmem_unused_huge_requeue() (suggested by Baolin)
- keep the move_back label in shmem_unused_huge_shrink() (suggested by Baolin)
- rebase onto the next-20260731
Changes in v2:
- temporarily add the dependent patch from Usama to the series for review convenience
- remove shrinklist_scan and shrinklist_isolated from struct shmem_inode_info, and re-implement the logic by resuing the same info->shrinklist (suggested by Baolin)
- add more comments (suggested by Andrew)
- fix missing initialization of info->shrinklist_memcg (pointed by sashiko)
- rebase onto the next-20260717
Hi all,
The shmem unused huge shrinker maintains a per-superblock list of inodes whose tail huge folio extends beyond i_size. Because this list is not memcg aware, reclaim triggered by memcg A can scan inodes across the entire superblock and split huge folios charged to unrelated memcg B, causing unexpected impact on it.
In the worst case, memcg A has no reclaimable shmem at all, making the reclaim entirely useless and incurring unnecessary latency. We observed this in production, where page lock contention during split caused multi-hundred-millisecond stalls:
tid 11340 comm scanner locked a page for 182264 us! kstack:
unlock_page+1
split_huge_page_to_list+3135
shmem_unused_huge_shrink+767
super_cache_scan+329
do_shrink_slab+291
shrink_slab+533
shrink_node+400
do_try_to_free_pages+206
try_to_free_mem_cgroup_pages+262
try_charge_memcg+591
mem_cgroup_charge+136
__handle_mm_fault+2431
handle_mm_fault+194
do_user_addr_fault+462
__do_page_fault+176
do_page_fault+48
page_fault+62Usama's recent patch [1] prevents the shmem unused shrinker from being invoked during memcg-level reclaim altogether, but this is overly
conservative: we can do better by reclaiming only the shmem charged to the reclaiming memcg.
This series converts the shrinker list to a memcg-aware list_lru, so that non-root memcg reclaim walks only candidates charged to the reclaiming memcg. Global reclaim, root memcg reclaim and shmem quota reclaim retain their existing global semantics.
To avoid pinning a dying memcg through a long-lived CSS reference, each inode stores an obj_cgroup reference instead of a mem_cgroup reference. The list_lru add/delete paths resolve the current memcg from the objcg under RCU, staying consistent with list_lru's own memcg migration on offline.
Thanks,
Qi
[1]. https://lore.kernel.org/all/[email protected]/
Qi Zheng (4):
fs: fix missed removal of super_fs_objects_eligible()
mm: memcontrol: make obj_cgroup_memcg() handle NULL objcg
mm: shmem: move unused huge shrinklist queuing past the truncation
check
mm: shmem: make unused huge shrinker memcg aware
fs/super.c | 18 +- include/linux/memcontrol.h | 11 +- include/linux/shmem_fs.h | 12 +- mm/page_owner.c | 2 +- mm/shmem.c | 382 ++++++++++++++++++++++++++++--------- mm/zswap.c | 17 +- 6 files changed, 315 insertions(+), 127 deletions(-)
Signature · 1 line
2.54.0
PATCH v4 1/4 fs: fix missed removal of super_fs_objects_eligible()
From: Qi Zheng <[email protected]>
Commit 0ef8faff490be ("fs: push nr_cached_objects memcg gating into individual filesystems") forgot to drop the super_fs_objects_eligible(), just fix it.
--- fs/super.c | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-)
fs/super.c +2 -16
@@ -171,19 +171,6 @@ static void super_wake(struct super_block *sb, unsigned int flag)wake_up_var (&sb->s_flags); } -/* -* The s_op->nr_cached_objects hooks (used for example by btrfs and xfs) -* operate on filesystem-global state and ignore sc->memcg. Driving them -* from per-memcg shrink_slab_memcg() invocations only burns CPU walking -* per-cpu counters and queueing duplicate work: the actual reclaim happens on -* the global path (kswapd or root direct reclaim) regardless. Restrict them -* to that path. -*/ -static inline bool super_fs_objects_eligible (struct shrink_control *sc) -{ -return !sc->memcg ||mem_cgroup_is_root (sc->memcg); -} -/* * One thing we have to be careful of with a per-sb shrinker is that we don't * drop the last active reference to the superblock from within the shrinker.
@@ -213,7 +200,7 @@ static unsigned long super_cache_scan(struct shrinker *shrink,if (!super_trylock_shared (sb))return SHRINK_STOP ; -if (sb->s_op->nr_cached_objects &&super_fs_objects_eligible (sc)) +if (sb->s_op->nr_cached_objects) fs_objects = sb->s_op->nr_cached_objects (sb, sc); inodes =list_lru_shrink_count (&sb->s_inode_lru, sc);
@@ -274,8 +261,7 @@ static unsigned long super_cache_count(struct shrinker *shrink,return 0 ;smp_rmb (); -if (sb->s_op && sb->s_op->nr_cached_objects && -super_fs_objects_eligible (sc)) +if (sb->s_op && sb->s_op->nr_cached_objects) total_objects = sb->s_op->nr_cached_objects (sb, sc); total_objects +=list_lru_shrink_count (&sb->s_dentry_lru, sc);
Signature · 1 line
2.54.0
Andrew Morton
maintainerRe: [PATCH v4 1/4] fs: fix missed removal of super_fs_objects_eligible()
Quoted 5 lines · On Mon, 17 Aug 2026 17:03:25 +0800 Qi Zheng <[email protected]> wrote:
From: Qi Zheng <[email protected]>
Commit
0ef8faff490be("fs: pushnr_cached_objectsmemcg gating into individual filesystems") forgot to drop thesuper_fs_objects_eligible(), just fix it.
Oh. Why? Please can we have a description of what's wrong with the current code?
Re: [PATCH v4 1/4] fs: fix missed removal of super_fs_objects_eligible()
Hi Andrew,
Quoted 10 lines · Andrew Morton
Quoted 5 lines · On Mon, 17 Aug 2026 17:03:25 +0800 Qi Zheng <[email protected]> wrote:
From: Qi Zheng <[email protected]>
Commit
0ef8faff490be("fs: pushnr_cached_objectsmemcg gating into individual filesystems") forgot to drop thesuper_fs_objects_eligible(), just fix it.Oh. Why? Please can we have a description of what's wrong with the current code?
As I replied in another thread [1], this fix and commit 0ef8faff490be together make up the complete patch originally sent by Usama [2], so they should be squashed together. Without this fix, all memcg-aware nr_cached_objects() will be skipped during memcg-level shrinking. Besides the newly added memcg-aware shmem huge shrinker, XFS will also run into issues.
[1]. https://lore.kernel.org/all/[email protected]/
[2]. https://lore.kernel.org/all/[email protected]/
Thanks,
Qi
Quoted 1 line
Andrew Morton
maintainerRe: [PATCH v4 1/4] fs: fix missed removal of super_fs_objects_eligible()
Quoted 11 lines · On Sat, 29 Aug 2026 09:49:44 +0800 Qi Zheng <[email protected]> wrote:
Hi Andrew,
Quoted 8 lines · Andrew Morton
Quoted 5 lines · On Mon, 17 Aug 2026 17:03:25 +0800 Qi Zheng <[email protected]> wrote:
From: Qi Zheng <[email protected]>
Commit
0ef8faff490be("fs: pushnr_cached_objectsmemcg gating into individual filesystems") forgot to drop thesuper_fs_objects_eligible(), just fix it.
Quoted 6 lines
Quoted 2 lines
Oh. Why? Please can we have a description of what's wrong with the current code?
As I replied in another thread [1], this fix and commit
0ef8faff490betogether make up the complete patch originally sent by Usama [2], so they should be squashed together.
0ef8faff490be is upstream so they can't be squashed!
Quoted 4 lines
Without this fix, all memcg-aware
nr_cached_objects()will be skipped during memcg-level shrinking. Besides the newly added memcg-aware shmem huge shrinker, XFS will also run into issues.
OK, please add this info to this patch's changelog. Please also decide whether a cc:stable should be added and if so, ensure the changelog makes clear why we're recommending a backport.
As part of this, let's expand on "XFS will also run into issues".
Thanks.
Re: [PATCH v4 1/4] fs: fix missed removal of super_fs_objects_eligible()
Hi Andrew,
Quoted 13 lines · Andrew Morton
Quoted 11 lines · On Sat, 29 Aug 2026 09:49:44 +0800 Qi Zheng <[email protected]> wrote:
Hi Andrew,
Quoted 8 lines · Andrew Morton
Quoted 5 lines · On Mon, 17 Aug 2026 17:03:25 +0800 Qi Zheng <[email protected]> wrote:
From: Qi Zheng <[email protected]>
Commit
0ef8faff490be("fs: pushnr_cached_objectsmemcg gating into individual filesystems") forgot to drop thesuper_fs_objects_eligible(), just fix it.
Quoted 8 lines
Quoted 6 lines
Quoted 2 lines
Oh. Why? Please can we have a description of what's wrong with the current code?
As I replied in another thread [1], this fix and commit
0ef8faff490betogether make up the complete patch originally sent by Usama [2], so they should be squashed together.
0ef8faff490beis upstream so they can't be squashed!
So sad.
Quoted 11 lines
Quoted 5 lines
Without this fix, all memcg-aware
nr_cached_objects()will be skipped during memcg-level shrinking. Besides the newly added memcg-aware shmem huge shrinker, XFS will also run into issues.OK, please add this info to this patch's changelog. Please also decide whether a cc:stable should be added and if so, ensure the changelog makes clear why we're recommending a backport.
As part of this, let's expand on "XFS will also run into issues".
How about this instead:
---
Commit 0ef8faff490be ("fs: push nr_cached_objects memcg gating into individual filesystems") was meant to drop the blanket memcg gate in fs/super.c and let each ->nr_cached_objects() implementation decide for itself whether it is meaningful in per-memcg reclaim. However, when that patch was applied the removal of super_fs_objects_eligible() and its two call sites in super_cache_scan() / super_cache_count() was lost, so the helper is still gating every ->nr_cached_objects() hook and 0ef8faff490be is effectively a no-op.
Consequences of the leftover gate:
- XFS's inode-reclaim hook, which is intentionally driven from per-memcg contexts to free memcg-charged slab, is still short-circuited in fs/super.c — exactly the regression from commit 0baad6f9b997 ("fs/super: skip non-memcg-aware nr_cached_objects in memcg slab shrink") that 0ef8faff490be was written to undo. Memcg-charged XFS inode slab therefore keeps piling up under per-memcg pressure until global reclaim kicks in.
- Any future ->nr_cached_objects()/->free_cached_objects() that grows memcg awareness is likewise blocked before it can run, so filesystems cannot opt in to per-memcg reclaim on their own — defeating the whole point of pushing the gating decision down into the callbacks.
Drop the leftover helper and its call sites so the intent of 0ef8faff490be actually takes effect.
---
Can you fix up the commit message directly on your end? Let me know if you prefer me to spin a v5 instead.
Thank,
Qi
Quoted 3 lines
Thanks.
Andrew Morton
maintainerRe: [PATCH v4 1/4] fs: fix missed removal of super_fs_objects_eligible()
Quoted 37 lines · On Mon, 31 Aug 2026 10:28:00 +0800 Qi Zheng <[email protected]> wrote:
Quoted 5 lines
OK, please add this info to this patch's changelog. Please also decide whether a cc:stable should be added and if so, ensure the changelog makes clear why we're recommending a backport.
As part of this, let's expand on "XFS will also run into issues".
How about this instead:
---
Commit0ef8faff490be("fs: pushnr_cached_objectsmemcg gating into individual filesystems") was meant to drop the blanket memcg gate infs/super.cand let each ->nr_cached_objects() implementation decide for itself whether it is meaningful in per-memcg reclaim. However, when that patch was applied the removal ofsuper_fs_objects_eligible()and its two call sites insuper_cache_scan()/super_cache_count()was lost, so the helper is still gating every ->nr_cached_objects() hook and0ef8faff490beis effectively a no-op.Consequences of the leftover gate:
- XFS's inode-reclaim hook, which is intentionally driven from per-memcg contexts to free memcg-charged slab, is still short-circuited in
fs/super.c— exactly the regression from commit0baad6f9b997("fs/super: skip non-memcg-awarenr_cached_objectsin memcg slab shrink") that0ef8faff490bewas written to undo. Memcg-charged XFS inode slab therefore keeps piling up under per-memcg pressure until global reclaim kicks in.- Any future ->nr_cached_objects()/->free_cached_objects() that grows memcg awareness is likewise blocked before it can run, so filesystems cannot opt in to per-memcg reclaim on their own — defeating the whole point of pushing the gating decision down into the callbacks.
Drop the leftover helper and its call sites so the intent of
0ef8faff490beactually takes effect.
Quoted 7 lines
Fixes 0ef8faff490be ("fs: push nr_cached_objects memcg gating intoindividual filesystems")
Cc: [email protected]
---Can you fix up the commit message directly on your end? Let me know if you prefer me to spin a v5 instead.
Thanks. I pasted this in and turned your [1/4] into a standalone patch for mm-hotfixes, with a cc:stable.
The remaining three patches I turned into a separate series for the next merge window.
This particular patch has no evidence of reviewer input yet?
From: Qi Zheng <[email protected]>
Subject: fs: fix missed removal of super_fs_objects_eligible()
Date: Mon, 17 Aug 2026 17:03:25 +0800
Commit 0ef8faff490be ("fs: push nr_cached_objects memcg gating into individual filesystems") was meant to drop the blanket memcg gate in fs/super.c and let each ->nr_cached_objects() implementation decide for itself whether it is meaningful in per-memcg reclaim. However, when that patch was applied the removal of super_fs_objects_eligible() and its two call sites in super_cache_scan() / super_cache_count() was lost, so the helper is still gating every ->nr_cached_objects() hook and 0ef8faff490be is effectively a no-op.
Consequences of the leftover gate:
- XFS's inode-reclaim hook, which is intentionally driven from per-memcg contexts to free memcg-charged slab, is still short-circuited in fs/super.c exactly the regression from commit 0baad6f9b997 ("fs/super: skip non-memcg-aware nr_cached_objects in memcg slab shrink") that 0ef8faff490be was written to undo. Memcg-charged XFS inode slab therefore keeps piling up under per-memcg pressure until global reclaim kicks in.
- Any future ->nr_cached_objects()/->free_cached_objects() that grows memcg awareness is likewise blocked before it can run, so filesystems cannot opt in to per-memcg reclaim on their own defeating the whole point of pushing the gating decision down into the callbacks.
Drop the leftover helper and its call sites so the intent of 0ef8faff490be actually takes effect.
---
fs/super.c | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-)
fs/super.c renamed from fs/super.c~fs-fix-missed-removal-of-super_fs_objects_eligible +2 -16
@@ -172,19 +172,6 @@ static void super_wake(struct super_bloc }/* -* The s_op->nr_cached_objects hooks (used for example by btrfs and xfs) -* operate on filesystem-global state and ignore sc->memcg. Driving them -* from per-memcg shrink_slab_memcg() invocations only burns CPU walking -* per-cpu counters and queueing duplicate work: the actual reclaim happens on -* the global path (kswapd or root direct reclaim) regardless. Restrict them -* to that path. -*/ -static inline bool super_fs_objects_eligible (struct shrink_control *sc) -{ -return !sc->memcg ||mem_cgroup_is_root (sc->memcg); -} - -/* * One thing we have to be careful of with a per-sb shrinker is that we don't * drop the last active reference to the superblock from within the shrinker. * If that happens we could trigger unregistering the shrinker from within the
@@ -213,7 +200,7 @@ static unsigned long super_cache_scan(stif (!super_trylock_shared (sb))return SHRINK_STOP ; -if (sb->s_op->nr_cached_objects &&super_fs_objects_eligible (sc)) +if (sb->s_op->nr_cached_objects) fs_objects = sb->s_op->nr_cached_objects (sb, sc); inodes =list_lru_shrink_count (&sb->s_inode_lru, sc);
@@ -274,8 +261,7 @@ static unsigned long super_cache_count(sreturn 0 ;smp_rmb (); -if (sb->s_op && sb->s_op->nr_cached_objects && -super_fs_objects_eligible (sc)) +if (sb->s_op && sb->s_op->nr_cached_objects) total_objects = sb->s_op->nr_cached_objects (sb, sc); total_objects +=list_lru_shrink_count (&sb->s_dentry_lru, sc);
_
Re: [PATCH v4 1/4] fs: fix missed removal of super_fs_objects_eligible()
Quoted 44 lines · Andrew Morton
Quoted 42 lines · On Mon, 31 Aug 2026 10:28:00 +0800 Qi Zheng <[email protected]> wrote:
Quoted 5 lines
OK, please add this info to this patch's changelog. Please also decide whether a cc:stable should be added and if so, ensure the changelog makes clear why we're recommending a backport.
As part of this, let's expand on "XFS will also run into issues".
How about this instead:
---
Commit0ef8faff490be("fs: pushnr_cached_objectsmemcg gating into individual filesystems") was meant to drop the blanket memcg gate infs/super.cand let each ->nr_cached_objects() implementation decide for itself whether it is meaningful in per-memcg reclaim. However, when that patch was applied the removal ofsuper_fs_objects_eligible()and its two call sites insuper_cache_scan()/super_cache_count()was lost, so the helper is still gating every ->nr_cached_objects() hook and0ef8faff490beis effectively a no-op.Consequences of the leftover gate:
- XFS's inode-reclaim hook, which is intentionally driven from per-memcg contexts to free memcg-charged slab, is still short-circuited in
fs/super.c— exactly the regression from commit0baad6f9b997("fs/super: skip non-memcg-awarenr_cached_objectsin memcg slab shrink") that0ef8faff490bewas written to undo. Memcg-charged XFS inode slab therefore keeps piling up under per-memcg pressure until global reclaim kicks in.- Any future ->nr_cached_objects()/->free_cached_objects() that grows memcg awareness is likewise blocked before it can run, so filesystems cannot opt in to per-memcg reclaim on their own — defeating the whole point of pushing the gating decision down into the callbacks.
Drop the leftover helper and its call sites so the intent of
0ef8faff490beactually takes effect.Fixes 0ef8faff490be ("fs: push nr_cached_objects memcg gating intoindividual filesystems")
Cc: [email protected]
---
Quoted 5 lines
Quoted 2 lines
Can you fix up the commit message directly on your end? Let me know if you prefer me to spin a v5 instead.
Thanks. I pasted this in and turned your [1/4] into a standalone patch for mm-hotfixes, with a cc:stable.
Thanks!
Quoted 3 lines
The remaining three patches I turned into a separate series for the next merge window.
OK.
Quoted 2 lines
This particular patch has no evidence of reviewer input yet?
Hi Usama, could you please help review this patch?
Thanks,
Qi
Quoted 101 lines
From: Qi Zheng <[email protected]>
Subject: fs: fix missed removal ofsuper_fs_objects_eligible()
Date: Mon, 17 Aug 2026 17:03:25 +0800Commit
0ef8faff490be("fs: pushnr_cached_objectsmemcg gating into individual filesystems") was meant to drop the blanket memcg gate infs/super.cand let each ->nr_cached_objects() implementation decide for itself whether it is meaningful in per-memcg reclaim. However, when that patch was applied the removal ofsuper_fs_objects_eligible()and its two call sites insuper_cache_scan()/super_cache_count()was lost, so the helper is still gating every ->nr_cached_objects() hook and0ef8faff490beis effectively a no-op.Consequences of the leftover gate:
- XFS's inode-reclaim hook, which is intentionally driven from per-memcg contexts to free memcg-charged slab, is still short-circuited in
fs/super.cexactly the regression from commit0baad6f9b997("fs/super: skip non-memcg-awarenr_cached_objectsin memcg slab shrink") that0ef8faff490bewas written to undo. Memcg-charged XFS inode slab therefore keeps piling up under per-memcg pressure until global reclaim kicks in.- Any future ->nr_cached_objects()/->free_cached_objects() that grows memcg awareness is likewise blocked before it can run, so filesystems cannot opt in to per-memcg reclaim on their own defeating the whole point of pushing the gating decision down into the callbacks.
Drop the leftover helper and its call sites so the intent of
0ef8faff490beactually takes effect.Link https://lore.kernel.org/3b038d373c70ebac7cdabfb0035bb91d1d6e6cfe.1786955972.git.zhengqi.arch@bytedance.com open hereFixes 0ef8faff490b ("fs: push nr_cached_objects memcg gating into individual filesystems")Signed-off-by Qi Zheng <[email protected]>Cc Baolin Wang <[email protected]>Cc Christian Brauner <[email protected]>Cc David Hildenbrand <[email protected]>Cc Hugh Dickins <[email protected]>Cc Christian Brauner <[email protected]>Cc David Hildenbrand <[email protected]>Cc Hugh Dickins <[email protected]>Cc Johannes Weiner <[email protected]>Cc Michal Hocko <[email protected]>Cc Muchun Song <[email protected]>Cc Roman Gushchin <[email protected]>Cc Shakeel Butt <[email protected]>Cc <[email protected]>Signed-off-by Andrew Morton <[email protected]>---
fs/super.c | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-)fs/super.c renamed from fs/super.c~fs-fix-missed-removal-of-super_fs_objects_eligible +2
-16@@ -172,19 +172,6 @@ static void super_wake(struct super_bloc }/* -* The s_op->nr_cached_objects hooks (used for example by btrfs and xfs) -* operate on filesystem-global state and ignore sc->memcg. Driving them -* from per-memcg shrink_slab_memcg() invocations only burns CPU walking -* per-cpu counters and queueing duplicate work: the actual reclaim happens on -* the global path (kswapd or root direct reclaim) regardless. Restrict them -* to that path. -*/ -static inline bool super_fs_objects_eligible (struct shrink_control *sc) -{ -return !sc->memcg ||mem_cgroup_is_root (sc->memcg); -} - -/* * One thing we have to be careful of with a per-sb shrinker is that we don't * drop the last active reference to the superblock from within the shrinker. * If that happens we could trigger unregistering the shrinker from within the @@ -213,7 +200,7 @@ static unsigned long super_cache_scan(stif (!super_trylock_shared (sb))return SHRINK_STOP ; -if (sb->s_op->nr_cached_objects &&super_fs_objects_eligible (sc)) +if (sb->s_op->nr_cached_objects) fs_objects = sb->s_op->nr_cached_objects (sb, sc); inodes =list_lru_shrink_count (&sb->s_inode_lru, sc);@@ -274,8 +261,7 @@ static unsigned long super_cache_count(sreturn 0 ;smp_rmb (); -if (sb->s_op && sb->s_op->nr_cached_objects && -super_fs_objects_eligible (sc)) +if (sb->s_op && sb->s_op->nr_cached_objects) total_objects = sb->s_op->nr_cached_objects (sb, sc); total_objects +=list_lru_shrink_count (&sb->s_dentry_lru, sc);_
Usama Arif
Acked-byRe: [PATCH v4 1/4] fs: fix missed removal of super_fs_objects_eligible()
In reply to Andrew MortonQuoted 107 lines · Andrew Morton
Quoted 37 lines · On Mon, 31 Aug 2026 10:28:00 +0800 Qi Zheng <[email protected]> wrote:
Quoted 5 lines
OK, please add this info to this patch's changelog. Please also decide whether a cc:stable should be added and if so, ensure the changelog makes clear why we're recommending a backport.
As part of this, let's expand on "XFS will also run into issues".
How about this instead:
---
Commit0ef8faff490be("fs: pushnr_cached_objectsmemcg gating into individual filesystems") was meant to drop the blanket memcg gate infs/super.cand let each ->nr_cached_objects() implementation decide for itself whether it is meaningful in per-memcg reclaim. However, when that patch was applied the removal ofsuper_fs_objects_eligible()and its two call sites insuper_cache_scan()/super_cache_count()was lost, so the helper is still gating every ->nr_cached_objects() hook and0ef8faff490beis effectively a no-op.Consequences of the leftover gate:
- XFS's inode-reclaim hook, which is intentionally driven from per-memcg contexts to free memcg-charged slab, is still short-circuited in
fs/super.c— exactly the regression from commit0baad6f9b997("fs/super: skip non-memcg-awarenr_cached_objectsin memcg slab shrink") that0ef8faff490bewas written to undo. Memcg-charged XFS inode slab therefore keeps piling up under per-memcg pressure until global reclaim kicks in.- Any future ->nr_cached_objects()/->free_cached_objects() that grows memcg awareness is likewise blocked before it can run, so filesystems cannot opt in to per-memcg reclaim on their own — defeating the whole point of pushing the gating decision down into the callbacks.
Drop the leftover helper and its call sites so the intent of
0ef8faff490beactually takes effect.Quoted 7 lines
Fixes 0ef8faff490be ("fs: push nr_cached_objects memcg gating intoindividual filesystems")
Cc: [email protected]
---Can you fix up the commit message directly on your end? Let me know if you prefer me to spin a v5 instead.
Thanks. I pasted this in and turned your [1/4] into a standalone patch for mm-hotfixes, with a cc:stable.
The remaining three patches I turned into a separate series for the next merge window.
This particular patch has no evidence of reviewer input yet?
From: Qi Zheng <[email protected]>
Subject: fs: fix missed removal ofsuper_fs_objects_eligible()
Date: Mon, 17 Aug 2026 17:03:25 +0800Commit
0ef8faff490be("fs: pushnr_cached_objectsmemcg gating into individual filesystems") was meant to drop the blanket memcg gate infs/super.cand let each ->nr_cached_objects() implementation decide for itself whether it is meaningful in per-memcg reclaim. However, when that patch was applied the removal ofsuper_fs_objects_eligible()and its two call sites insuper_cache_scan()/super_cache_count()was lost, so the helper is still gating every ->nr_cached_objects() hook and0ef8faff490beis effectively a no-op.Consequences of the leftover gate:
- XFS's inode-reclaim hook, which is intentionally driven from per-memcg contexts to free memcg-charged slab, is still short-circuited in
fs/super.cexactly the regression from commit0baad6f9b997("fs/super: skip non-memcg-awarenr_cached_objectsin memcg slab shrink") that0ef8faff490bewas written to undo. Memcg-charged XFS inode slab therefore keeps piling up under per-memcg pressure until global reclaim kicks in.- Any future ->nr_cached_objects()/->free_cached_objects() that grows memcg awareness is likewise blocked before it can run, so filesystems cannot opt in to per-memcg reclaim on their own defeating the whole point of pushing the gating decision down into the callbacks.
Drop the leftover helper and its call sites so the intent of
0ef8faff490beactually takes effect.Link https://lore.kernel.org/3b038d373c70ebac7cdabfb0035bb91d1d6e6cfe.1786955972.git.zhengqi.arch@bytedance.com open hereFixes 0ef8faff490b ("fs: push nr_cached_objects memcg gating into individual filesystems")Signed-off-by Qi Zheng <[email protected]>Cc Baolin Wang <[email protected]>Cc Christian Brauner <[email protected]>Cc David Hildenbrand <[email protected]>Cc Hugh Dickins <[email protected]>Cc Christian Brauner <[email protected]>Cc David Hildenbrand <[email protected]>Cc Hugh Dickins <[email protected]>Cc Johannes Weiner <[email protected]>Cc Michal Hocko <[email protected]>Cc Muchun Song <[email protected]>Cc Roman Gushchin <[email protected]>Cc Shakeel Butt <[email protected]>Cc <[email protected]>Signed-off-by Andrew Morton <[email protected]>
Quoted 2 lines
---
Yeah unfortunately this was a mess up with a part of the patch not being applied.
PATCH v4 2/4 mm: memcontrol: make obj_cgroup_memcg() handle NULL objcg
In reply to Qi ZhengFrom: Qi Zheng <[email protected]>
obj_cgroup_memcg() currently requires a non-NULL objcg, so callers that may hold a NULL objcg must guard the call with an explicit NULL check. This pattern is duplicated in folio_memcg(), folio_memcg_check(), mm/page_owner.c, and mm/zswap.c.
Teach obj_cgroup_memcg() to accept NULL and return NULL in that case, then remove the redundant NULL checks at the call sites. Also remove the mem_cgroup_from_entry() wrapper in zswap, which existed solely to provide this NULL-safe behaviour, and replace its two callers with direct obj_cgroup_memcg() calls.
No functional change intended.
--- include/linux/memcontrol.h | 11 ++++++++--- mm/page_owner.c | 2 +- mm/zswap.c | 17 ++--------------- 3 files changed, 11 insertions(+), 19 deletions(-)
include/linux/memcontrol.h +8 -3
@@ -375,7 +375,7 @@ enum objext_flags {static inline struct mem_cgroup *obj_cgroup_memcg (struct obj_cgroup *objcg) {lockdep_assert_once (rcu_read_lock_held () ||lockdep_is_held (&cgroup_mutex)); -return READ_ONCE (objcg->memcg); +return objcg ?READ_ONCE (objcg->memcg) :NULL ; }/*
@@ -428,7 +428,7 @@ static inline struct mem_cgroup *folio_memcg(struct folio *folio) {struct obj_cgroup *objcg =folio_objcg (folio); -return objcg ?obj_cgroup_memcg (objcg) :NULL ; +return obj_cgroup_memcg (objcg); }/*
@@ -471,7 +471,7 @@ static inline struct mem_cgroup *folio_memcg_check(struct folio *folio) objcg = (void *)(memcg_data & ~OBJEXTS_FLAGS_MASK ); -return objcg ?obj_cgroup_memcg (objcg) :NULL ; +return obj_cgroup_memcg (objcg); }static inline struct mem_cgroup *page_memcg_check (struct page *page)
@@ -1045,6 +1045,11 @@ void mem_cgroup_flush_workqueue(void);extern int mem_cgroup_init (void );#else /* CONFIG_MEMCG */ +static inline struct mem_cgroup *obj_cgroup_memcg (struct obj_cgroup *objcg) +{ +return NULL ; +} +#define MEM_CGROUP_ID_SHIFT 0#define root_mem_cgroup (NULL)
mm/page_owner.c +1 -1
@@ -575,7 +575,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret, } objcg = (void *)(memcg_data & ~OBJEXTS_FLAGS_MASK ); - memcg = objcg ?obj_cgroup_memcg (objcg) :NULL ; + memcg =obj_cgroup_memcg (objcg);if (!memcg) goto out_unlock;
mm/zswap.c +2 -15
@@ -647,19 +647,6 @@ static int zswap_enabled_param_set(const char *val,* lru functions **********************************/ -/* should be called under RCU */ -#ifdef CONFIG_MEMCG -static inline struct mem_cgroup *mem_cgroup_from_entry (struct zswap_entry *entry) -{ -return entry->objcg ?obj_cgroup_memcg (entry->objcg) :NULL ; -} -#else -static inline struct mem_cgroup *mem_cgroup_from_entry (struct zswap_entry *entry) -{ -return NULL ; -} -#endif -static inline int entry_to_nid (struct zswap_entry *entry) {return page_to_nid (virt_to_page (entry));
@@ -682,7 +669,7 @@ static void zswap_lru_add(struct zswap_entry *entry)* Similar reasoning holds for list_lru_del(). */ rcu_read_lock (); - memcg =mem_cgroup_from_entry (entry); + memcg =obj_cgroup_memcg (entry->objcg);/* will always succeed */ list_lru_add (&zswap_list_lru, &entry->lru, nid, memcg);rcu_read_unlock ();
@@ -694,7 +681,7 @@ static void zswap_lru_del(struct zswap_entry *entry)struct mem_cgroup *memcg;rcu_read_lock (); - memcg =mem_cgroup_from_entry (entry); + memcg =obj_cgroup_memcg (entry->objcg);/* will always succeed */ list_lru_del (&zswap_list_lru, &entry->lru, nid, memcg);rcu_read_unlock ();
Signature · 1 line
2.54.0
Re: [PATCH v4 2/4] mm: memcontrol: make obj_cgroup_memcg() handle NULL objcg
CC the maintainers of MEMCG who were missed in the previous email.
Re: [PATCH v4 2/4] mm: memcontrol: make obj_cgroup_memcg() handle NULL objcg
In reply to Qi ZhengQuoted 13 lines · Qi Zheng
From: Qi Zheng <[email protected]>
obj_cgroup_memcg()currently requires a non-NULL objcg, so callers that may hold a NULL objcg must guard the call with an explicit NULL check. This pattern is duplicated infolio_memcg(),folio_memcg_check(),mm/page_owner.c, andmm/zswap.c.Teach
obj_cgroup_memcg()to accept NULL and return NULL in that case, then remove the redundant NULL checks at the call sites. Also remove themem_cgroup_from_entry()wrapper in zswap, which existed solely to provide this NULL-safe behaviour, and replace its two callers with directobj_cgroup_memcg()calls.
Quoted 3 lines
No functional change intended.
Signed-off-by Qi Zheng <[email protected]>
PATCH v4 3/4 mm: shmem: move unused huge shrinklist queuing past the truncation check
In reply to Qi ZhengFrom: Qi Zheng <[email protected]>
The shmem_get_folio_gfp() adds the inode to the unused huge shrinker list at the alloced label, but a subsequent truncation check may still fail and remove the folio, leaving the inode on the list with a stale folio.
The original code works because the shrinker re-looks-up the folio and drops stale entries, but it is cleaner to queue the inode only after all checks that might remove the folio have passed.
So just make the pure structural move with no functional change, and it serves as preparation for the memcg-aware shrinker conversion.
--- mm/shmem.c | 48 +++++++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 21 deletions(-)
mm/shmem.c +27 -21
@@ -2536,27 +2536,6 @@ static int shmem_get_folio_gfp(struct inode *inode, pgoff_t index, alloced: alloced = true; -if (folio_test_large (folio) && -DIV_ROUND_UP (i_size_read (inode),PAGE_SIZE ) < -folio_next_index (folio)) { -struct shmem_sb_info *sbinfo =SHMEM_SB (inode->i_sb); -struct shmem_inode_info *info =SHMEM_I (inode); -/* -* Part of the large folio is beyond i_size: subject -* to shrink under memory pressure. -*/ -spin_lock (&sbinfo->shrinklist_lock); -/* -* _careful to defend against unlocked access to -* ->shrink_list in shmem_unused_huge_shrink() -*/ -if (list_empty_careful (&info->shrinklist)) { -list_add_tail (&info->shrinklist, - &sbinfo->shrinklist); - sbinfo->shrinklist_len++; - } -spin_unlock (&sbinfo->shrinklist_lock); - }if (sgp ==SGP_WRITE )folio_set_referenced (folio);
@@ -2586,6 +2565,33 @@ static int shmem_get_folio_gfp(struct inode *inode, pgoff_t index, error = -EINVAL ; goto unlock; } + +/* +* Queue the inode on the shrink list only after all checks that might +* remove the folio have passed. Otherwise the inode could be left on +* the shrinker list with a stale folio. +*/ +if (alloced &&folio_test_large (folio) && +DIV_ROUND_UP (i_size_read (inode),PAGE_SIZE ) <folio_next_index (folio)) { +struct shmem_sb_info *sbinfo =SHMEM_SB (inode->i_sb); +struct shmem_inode_info *info =SHMEM_I (inode); +/* +* Part of the large folio is beyond i_size: subject +* to shrink under memory pressure. +*/ +spin_lock (&sbinfo->shrinklist_lock); +/* +* _careful to defend against unlocked access to +* ->shrink_list in shmem_unused_huge_shrink() +*/ +if (list_empty_careful (&info->shrinklist)) { +list_add_tail (&info->shrinklist, + &sbinfo->shrinklist); + sbinfo->shrinklist_len++; + } +spin_unlock (&sbinfo->shrinklist_lock); + } + out: *foliop = folio;return 0 ;
Signature · 1 line
2.54.0
PATCH v4 4/4 mm: shmem: make unused huge shrinker memcg aware
In reply to Qi ZhengFrom: Qi Zheng <[email protected]>
The shmem unused huge shrinker keeps a per-superblock list of inodes whose tail huge folio extends beyond i_size. Since that list is not memcg aware, reclaim triggered by one memcg can scan inodes from the whole superblock and split shmem huge folios charged to unrelated memcgs.
Convert the shrink list to a memcg-aware list_lru. Queue each inode on the list_lru sublist matching the memcg and node of the current tail huge folio, so non-root memcg reclaim only walks candidates charged to the reclaiming memcg. Global reclaim, root memcg reclaim and shmem quota reclaim keep global semantics.
Rather than pinning a struct mem_cgroup reference in shmem_inode_info, store a struct obj_cgroup reference instead. The list_lru add and delete paths resolve the current memcg from the objcg under RCU, so that memcg offline and list_lru entry migration remain consistent: list_lru migrates entries to the parent memcg sublist on offline, and obj_cgroup_memcg() follows the same reparenting, ensuring the correct sublist is always found at delete time. This avoids pinning a dying memcg through a long-lived CSS reference.
The list_lru still tracks inodes while the actual split target is the current tail huge folio, so validate the folio memcg/node during scan. If the folio no longer matches the reclaim context or splitting cannot proceed, requeue the inode according to the current tail folio; if the inode is no longer shrinkable, drop the scan entry.
This can be tested with the shrinker debugfs interface by allocating 32 tmpfs tail THPs in each of two memcgs, then scanning the sb-tmpfs shrinker with memcg A's cgroup id:
before A scan after A scan base A=64M, B=64M A=64M, B=64M (per-memcg count is skipped) patched A=64M, B=64M A=0, B=64M
--- include/linux/shmem_fs.h | 12 +- mm/shmem.c | 362 ++++++++++++++++++++++++++++++--------- 2 files changed, 289 insertions(+), 85 deletions(-)
include/linux/shmem_fs.h +9 -3
@@ -11,6 +11,7 @@#include <linux/fs_parser.h> #include <linux/userfaultfd_k.h> #include <linux/bits.h> +#include <linux/list_lru.h> /* inode in-kernel data */
@@ -54,6 +55,11 @@ struct shmem_inode_info {struct dquot __rcu *i_dquot[MAXQUOTAS ];#endif struct inode vfs_inode; + +#ifdef CONFIG_TRANSPARENT_HUGEPAGE +struct obj_cgroup *shrinklist_objcg; +int shrinklist_nid; +#endif };#define SHMEM_FL_USER_VISIBLE (FS_FL_USER_VISIBLE | FS_CASEFOLD_FL)
@@ -83,9 +89,9 @@ struct shmem_sb_info {ino_t next_ino;/* The next per-sb inode number to use */ ino_t __percpu *ino_batch;/* The next per-cpu inode number to use */ struct mempolicy *mpol;/* default memory policy for mappings */ -spinlock_t shrinklist_lock;/* Protects shrinklist */ -struct list_head shrinklist;/* List of shinkable inodes */ -unsigned long shrinklist_len;/* Length of shrinklist */ +#ifdef CONFIG_TRANSPARENT_HUGEPAGE +struct list_lru shrinklist;/* List of shrinkable inodes */ +#endif struct shmem_quota_limits qlimits;/* Default quota limits */ struct simple_xattr_cache xa_cache; };
mm/shmem.c +280 -82
466 lines not on this page · raw message
Signature · 1 line
2.54.0
Re: [PATCH v4 4/4] mm: shmem: make unused huge shrinker memcg aware
Quoted 32 lines · Qi Zheng
From: Qi Zheng <[email protected]>
The shmem unused huge shrinker keeps a per-superblock list of inodes whose tail huge folio extends beyond
i_size. Since that list is not memcg aware, reclaim triggered by one memcg can scan inodes from the whole superblock and split shmem huge folios charged to unrelated memcgs.Convert the shrink list to a memcg-aware
list_lru. Queue each inode on thelist_lrusublist matching the memcg and node of the current tail huge folio, so non-root memcg reclaim only walks candidates charged to the reclaiming memcg. Global reclaim, root memcg reclaim and shmem quota reclaim keep global semantics.Rather than pinning a struct
mem_cgroupreference inshmem_inode_info, store a structobj_cgroupreference instead. Thelist_lruadd and delete paths resolve the current memcg from the objcg under RCU, so that memcg offline andlist_lruentry migration remain consistent:list_lrumigrates entries to the parent memcg sublist on offline, andobj_cgroup_memcg()follows the same reparenting, ensuring the correct sublist is always found at delete time. This avoids pinning a dying memcg through a long-lived CSS reference.The
list_lrustill tracks inodes while the actual split target is the current tail huge folio, so validate the folio memcg/node during scan. If the folio no longer matches the reclaim context or splitting cannot proceed, requeue the inode according to the current tail folio; if the inode is no longer shrinkable, drop the scan entry.This can be tested with the shrinker debugfs interface by allocating 32 tmpfs tail THPs in each of two memcgs, then scanning the sb-tmpfs shrinker with memcg A's cgroup id:
Quoted 6 lines
before A scan after A scan base A=64M, B=64M A=64M, B=64M (per-memcg count is skipped) patched A=64M, B=64M A=0, B=64MSigned-off-by Qi Zheng <[email protected]>---
Nothing else jumped out at me. Thanks.
Andrew Morton
maintainerQuoted 3 lines · On Mon, 17 Aug 2026 17:03:24 +0800 Qi Zheng <[email protected]> wrote:
Changes in v4:
Changes in v3:
Changes in v2:
Thanks for the diligent versioning info. fyi, it is conventional to maintain this below the --- separator. It's not really the most important part of the [0/N]!
Quoted 11 lines
The shmem unused huge shrinker maintains a per-superblock list of inodes whose tail huge folio extends beyond
i_size. Because this list is not memcg aware, reclaim triggered by memcg A can scan inodes across the entire superblock and split huge folios charged to unrelated memcg B, causing unexpected impact on it.In the worst case, memcg A has no reclaimable shmem at all, making the reclaim entirely useless and incurring unnecessary latency. We observed this in production, where page lock contention during split caused multi-hundred-millisecond stalls:
Ugh. That's the most important part!
Quoted 29 lines
tid 11340 comm scanner locked a page for 182264 us! kstack:
unlock_page+1 split_huge_page_to_list+3135 shmem_unused_huge_shrink+767 super_cache_scan+329 do_shrink_slab+291 shrink_slab+533 shrink_node+400 do_try_to_free_pages+206 try_to_free_mem_cgroup_pages+262 try_charge_memcg+591 mem_cgroup_charge+136 __handle_mm_fault+2431 handle_mm_fault+194 do_user_addr_fault+462 __do_page_fault+176 do_page_fault+48 page_fault+62Usama's recent patch [1] prevents the shmem unused shrinker from being invoked during memcg-level reclaim altogether, but this is overly
conservative: we can do better by reclaiming only the shmem charged to the reclaiming memcg.This series converts the shrinker list to a memcg-aware
list_lru, so that non-root memcg reclaim walks only candidates charged to the reclaiming memcg. Global reclaim, root memcg reclaim and shmem quota reclaim retain their existing global semantics.
Quoted 5 lines
To avoid pinning a dying memcg through a long-lived CSS reference, each inode stores an
obj_cgroupreference instead of amem_cgroupreference. Thelist_lruadd/delete paths resolve the current memcg from the objcg under RCU, staying consistent with list_lru's own memcg migration on offline.
Sashiko said a few things and they look disturbing-if-true:
https://sashiko.dev/#/patchset/[email protected]
(Apologies if this has already been considered - we don't have ways of tracking all this (yet, I hope) apart from personal memory and personal memorys are quite fried at present)
Hi Andrew,
Quoted 9 lines · Andrew Morton
Quoted 3 lines · On Mon, 17 Aug 2026 17:03:24 +0800 Qi Zheng <[email protected]> wrote:
Changes in v4:
Changes in v3:
Changes in v2:Thanks for the diligent versioning info. fyi, it is conventional to maintain this below the --- separator. It's not really the most important part of the [0/N]!
Got it.
Quoted 50 lines
Quoted 8 lines
The shmem unused huge shrinker maintains a per-superblock list of inodes whose tail huge folio extends beyond
i_size. Because this list is not memcg aware, reclaim triggered by memcg A can scan inodes across the entire superblock and split huge folios charged to unrelated memcg B, causing unexpected impact on it.Quoted 4 lines
In the worst case, memcg A has no reclaimable shmem at all, making the reclaim entirely useless and incurring unnecessary latency. We observed this in production, where page lock contention during split caused multi-hundred-millisecond stalls:
Ugh. That's the most important part!
Quoted 29 lines
tid 11340 comm scanner locked a page for 182264 us! kstack: unlock_page+1 split_huge_page_to_list+3135 shmem_unused_huge_shrink+767 super_cache_scan+329 do_shrink_slab+291 shrink_slab+533 shrink_node+400 do_try_to_free_pages+206 try_to_free_mem_cgroup_pages+262 try_charge_memcg+591 mem_cgroup_charge+136 __handle_mm_fault+2431 handle_mm_fault+194 do_user_addr_fault+462 __do_page_fault+176 do_page_fault+48 page_fault+62Usama's recent patch [1] prevents the shmem unused shrinker from being invoked during memcg-level reclaim altogether, but this is overly
conservative: we can do better by reclaiming only the shmem charged to the reclaiming memcg.This series converts the shrinker list to a memcg-aware
list_lru, so that non-root memcg reclaim walks only candidates charged to the reclaiming memcg. Global reclaim, root memcg reclaim and shmem quota reclaim retain their existing global semantics.Quoted 5 lines
To avoid pinning a dying memcg through a long-lived CSS reference, each inode stores an
obj_cgroupreference instead of amem_cgroupreference. Thelist_lruadd/delete paths resolve the current memcg from the objcg under RCU, staying consistent with list_lru's own memcg migration on offline.
Quoted 7 lines
Sashiko said a few things and they look disturbing-if-true:
https://sashiko.dev/#/patchset/[email protected](Apologies if this has already been considered - we don't have ways of tracking all this (yet, I hope) apart from personal memory and personal memorys are quite fried at present)
As both Usama and I have pointed out [1][2], [PATCH v4 1/4] is the part that got dropped during the merge. The complete patch [3] was actually reviewed a while ago.
[1].https://lore.kernel.org/all/[email protected]/ [2]. https://lore.kernel.org/all/[email protected]/
[3]. https://lore.kernel.org/all/[email protected]/
Thanks,
Qi