[RFC PATCH 1/1] mm: vmscan: keep anon scanning enabled when swapcache folios are present

Thomas Hellström
2026-04-27T10:35:58Z

Two separate guards in the reclaim path suppress all anon-LRU scanning
when swap space is exhausted or nearly so:

can_reclaim_anon_pages() – returns false when get_nr_swap_pages()==0
(or the memcg swap limit is reached);
used by the non-MGLRU get_scan_count() and
several MGLRU helper estimations.

get_swappiness() [MGLRU] – returns 0 when the above condition holds AND
the per-memcg free-swap count drops below
MIN_LRU_BATCH, causing for_each_evictable_type()
to skip LRU_GEN_ANON entirely.

Both guards exist for a good reason: if there is no swap space, trying to
write out an anon folio is pointless because folio_alloc_swap() will fail
before a slot can be assigned. The scan overhead is wasted and may delay
reclaiming more useful file-backed pages.

However, both guards conflate two conceptually different populations of
anon pages:

1. Anon pages that have never been backed by swap.
These require a free slot before they can be reclaimed.
The guards are correct for this group.

2. Anon folios that are already in the swap cache (PG_swapcache set).
These carry a pre-allocated swap slot. Writing them back and
dropping their RAM backing does NOT consume a new slot: the slot
already exists and its map-count is held at >= 1 (via
folio_dup_swap()) until the content has been safely stored.
Reclaiming such a folio frees nr_pages of RAM while leaving the
slot count unchanged.

Because the two guards do not distinguish between these groups, they
suppress anon scanning for the entire LRU once swap becomes scarce,
even when swapcache folios are present that could be reclaimed for free.
The practical consequence is that those folios remain in RAM,
blocking forward progress under memory pressure.

Fix this by continuing the scan while there are pages in the swap cache

RFC:
This might actually be intentional: accepting a presumably small number
of freeable pages in the swap cache to avoid costly LRU scans?

There might also be more efficient fixes, like introducing a separate
LRU list for swapcache pages?

Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: David Hildenbrand <david@kernel.org>
Cc: Michal Hocko <mhocko@kernel.org>
Cc: Qi Zheng <zhengqi.arch@bytedance.com>
Cc: Shakeel Butt <shakeel.butt@linux.dev>
Cc: Lorenzo Stoakes <ljs@kernel.org>
Cc: Axel Rasmussen <axelrasmussen@google.com>
Cc: Yuanchu Xie <yuanchu@google.com>
Cc: Wei Xu <weixugc@google.com>
Cc: Brian Geffon <bgeffon@google.com>
Cc: Steven Barrett <steven@liquorix.net>
Cc: Oleksandr Natalenko <oleksandr@natalenko.name>
Cc: linux-mm@kvack.org
Cc: linux-kernel@vger.kernel.org
Fixes: ac35a4902374 ("mm: multi-gen LRU: minimal implementation")
Fixes: a2a36488a61c ("mm/vmscan: Consider anonymous pages without swap")
Assisted-by: GitHub Copilot:claude-sonnet-4.6
Signed-off-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
---
mm/vmscan.c | 21 +++++++++++++++++++--
1 file changed, 19 insertions(+), 2 deletions(-)

diff --git a/mm/vmscan.c b/mm/vmscan.c
index 0fc9373e8251..f50a5fc99fc9 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -377,6 +377,15 @@ static inline bool can_reclaim_anon_pages(struct mem_cgroup *memcg,
return true;
}

+ /*
+ * Even with no free swap slots, anon folios already in the swap cache
+ * carry a pre-allocated slot and can be written back and freed from RAM
+ * without consuming a new one. Do not suppress anon scanning when such
+ * folios are present on this node.
+ */
+ if (node_page_state(NODE_DATA(nid), NR_SWAPCACHE) > 0)
+ return true;
+
/*
* The page can not be swapped.
*
@@ -2736,8 +2745,16 @@ static int get_swappiness(struct lruvec *lruvec, struct scan_control *sc)
return 0;

if (!can_demote(pgdat->node_id, sc, memcg) &&
- mem_cgroup_get_nr_swap_pages(memcg) < MIN_LRU_BATCH)
- return 0;
+ mem_cgroup_get_nr_swap_pages(memcg) < MIN_LRU_BATCH) {
+ /*
+ * Even with few free swap slots, folios already in the swap
+ * cache carry a pre-allocated slot and can be written back and
+ * freed from RAM without consuming a new one. Keep anon
+ * scanning enabled while such folios remain in this lruvec.
+ */
+ if (!lruvec_page_state(lruvec, NR_SWAPCACHE))
+ return 0;
+ }

return sc_swappiness(sc, memcg);
}
--
2.53.0