[PATCH] mm: list_lru: clear shrinker id when disabling memcg awareness

From: Wentao Guan

Date: Fri Sep 04 2026 - 15:14:39 EST


When cgroup.memory=nokmem is in effect, __list_lru_init() disables
memcg awareness for the list_lru, but it leaves lru->shrinker_id copied
from the supplied shrinker.

This used to be mostly harmless for memcg-aware slab list_lrus because,
with kmem accounting disabled, slab objects are not charged to memcgs and
list_lru_add_obj() passes NULL memcgs. The THP deferred split conversion
changed that: it initializes a memcg-aware list_lru with the THP shrinker
and later adds anon THPs with the folio memcg, even when kmem accounting
is disabled. The list_lru itself has been collapsed to plain per-node
lists, but the stale shrinker id still makes __list_lru_add() call
set_shrinker_bit().

This was reproduced on a kernel with CONFIG_MEMCG=y and
CONFIG_TRANSPARENT_HUGEPAGE=y, booted with kernel memory accounting
disabled. The same condition can be reproduced by booting with:

cgroup.memory=nokmem

and then faulting in anonymous THPs so that they are queued on the THP
deferred split list_lru. The warning showed that the memcg had no kmem id
and no memcg shrinker bitmap capacity, while the list_lru still carried a
valid shrinker id:

set_shrinker_bit: memcg=ffff8d23cbe60000 css_id=31 kmem_id=-1 \
dying=0 nid=0 shrinker_id=0 shrinker_nr_max=0 map_nr_max=0 \
info=ffff8d23d68c60c0
WARNING: mm/shrinker.c:218 at set_shrinker_bit+0xdb/0x100

The stack was:

set_shrinker_bit
__list_lru_add
deferred_split_folio
map_anon_folio_pmd_nopf
map_anon_folio_pmd_pf
__do_huge_pmd_anonymous_page
do_huge_pmd_anonymous_page
__handle_mm_fault
handle_mm_fault
do_user_addr_fault
exc_page_fault
asm_exc_page_fault

The list_lru code already uses shrinker_id == -1 as the sentinel that
prevents set_shrinker_bit() from doing any memcg bitmap operation. Keep
that invariant when memcg awareness is forced off due to disabled kmem
accounting.

This is related to the cgroup_disable=memory case fixed by Qinyun Tan,
where __list_lru_init() also needs to collapse list_lrus to plain
per-node lists when the memory controller is disabled entirely. That
patch makes the memcg awareness decision complete; this patch keeps the
shrinker id in sync with that decision.

Link: https://lore.kernel.org/linux-mm/20260902093202.609559-1-qinyuntan@xxxxxxxxxxxxxxxxx/
Fixes: fafaeceb89a5 ("mm: switch deferred split shrinker to list_lru")
Assisted-by: atomcode:gpt-5.5-medium
Signed-off-by: Wentao Guan <guanwentao@xxxxxxxxxxxxx>
---
mm/list_lru.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/mm/list_lru.c b/mm/list_lru.c
index 36662d02ff963..c08a5e2367106 100644
--- a/mm/list_lru.c
+++ b/mm/list_lru.c
@@ -671,8 +671,10 @@ int __list_lru_init(struct list_lru *lru, bool memcg_aware, struct shrinker *shr
else
lru->shrinker_id = -1;

- if (mem_cgroup_kmem_disabled())
+ if (mem_cgroup_kmem_disabled()) {
memcg_aware = false;
+ lru->shrinker_id = -1;
+ }
#endif

lru->node = kzalloc_objs(*lru->node, nr_node_ids);
--
2.30.2