From 83367658a01087375ede31ae343c99da03f72ac8 Mon Sep 17 00:00:00 2001 From: "Gregory Price (Meta)" Date: Mon, 19 Oct 2026 14:48:50 -0400 Subject: [PATCH] mm/page_alloc: let the highest memory node recover from hopeless state free_frozen_page_commit() clears a hopeless node's kswapd failures once its zone recovers above the high watermark, but only if another memory node exists for allocations to fall back to. The test `next_memory_node(nid) < MAX_NUMNODES` finds only memory nodes with a higher id. The highest-numbered memory node therefore never recovers on this path, and its kswapd stays disabled until reclaim on that node makes progress and balances it, or demotion is re-enabled. In many cases, direct reclaim many never fully run against a lower node (CXL/kmem), and that means its kswapd can end up full disabled in rare situations. To reproduce, fill the highest memory node with bound, non-THP anonymous memory and no swap until kswapd gives up, then free it: /proc/zoneinfo keeps showing node_unreclaimable 1 for that node. A lower node recovers. Count the memory nodes instead. Adding first_memory_node != nid to the existing test would also work, but a count says directly what the check means: some other memory node exists. Link: https://lore.kernel.org/20261019184850.3427905-1-gourry@gourry.net Fixes: e7a5f249e6db ("mm: re-enable kswapd when memory pressure subsides or demotion is toggled") Signed-off-by: Gregory Price (Meta) Signed-off-by: Andrew Morton Acked-by: Zi Yan Acked-by: Vlastimil Babka (SUSE) Cc: Suren Baghdasaryan Cc: Michal Hocko Cc: Brendan Jackman Cc: Johannes Weiner Cc: Chanwon Park Cc: --- mm/page_alloc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/page_alloc.c b/mm/page_alloc.c index a8cac5e01066..31ba24411d36 100644 --- a/mm/page_alloc.c +++ b/mm/page_alloc.c @@ -2929,7 +2929,7 @@ static bool free_frozen_page_commit(struct zone *zone, * kswapd work again by resetting kswapd_failures. */ if (kswapd_test_hopeless(pgdat) && - next_memory_node(pgdat->node_id) < MAX_NUMNODES) + num_node_state(N_MEMORY) > 1) kswapd_clear_hopeless(pgdat, KSWAPD_CLEAR_HOPELESS_PCP); } return ret;