Re: [PATCH mm-next] mm/swap: free swap cache folios the zap path gave up on

From: Kairui Song

Date: Thu Oct 15 2026 - 23:10:01 EST


On Wed, Oct 14, 2026 at 5:10 PM Jiayuan Chen <jiayuan.chen@xxxxxxxxx> wrote:
>
> From: Jiayuan Chen <jiayuan.chen@xxxxxxxxxx>
>
> On our production hosts we saw swap cache only pages keep growing for days:
> on a box with a 500 GiB swap partition, 250 GiB of RAM and 250 GiB of
> swap slots were held by folios that no process mapped any more, charged
> to cgroups whose workloads had long exited. The only way to get them
> back was swapoff, and with other services actively swapping on the
> same host that is not an option.

Hi, thanks for the report.

> Such a slot has swap count 0 and an unmapped folio pinned in the swap
> cache, left there after a process exited or did MADV_DONTNEED. No page
> table holds its swap entry any more, so nothing comes back for the
> slot; LRU reclaim drops it eventually, and the allocator only reclaims
> such slots once swap is half full. The growth stopped exactly there:
> at 50% of the device, where vm_swap_full() becomes true.

Hmm, I think this indicates a much cleaner fix is available.
vm_swap_full is inherited from the old allocator, we can instead just
let the allocator reclaim the slot if the slot have a count of 0 and
ignore vm_swap_full at that point.

Will this work? (Just a minimal implementation for reference, will be
even better if we can avoid the overhead).

diff --git a/mm/swapfile.c b/mm/swapfile.c
index c3288910b3e3..94670fe82f31 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -212,6 +212,9 @@ static int __try_to_reclaim_swap(struct
swap_info_struct *si,
nr_pages = folio_nr_pages(folio);
ret = -nr_pages;

+ if (folio_mapped(folio) && !vm_swap_full())
+ goto out;
+
/*
* We hold a folio lock here. We have to use trylock for
* avoiding deadlock. This is a special case and you should
@@ -903,8 +906,6 @@ static bool cluster_scan_range(struct swap_info_struct *si,
if (swp_tb_is_null(swp_tb))
continue;
if (swp_tb_is_folio(swp_tb) && !__swp_tb_get_count(swp_tb)) {
- if (!vm_swap_full())
- return false;
*need_reclaim = true;
continue;
}


Or, from the LRU's side, put swap readahead folios at the tail. I'm
actually trying to do that and it's not very easily doable at the
moment but will soon be easy to do.

> Options considered:
>
> 1. Fix it on the readahead side: after the read, check the swap count
> and free the slot. Covers the lock, not writeback, and the
> asynchronous read path unlocks in the bio completion where the slot
> cannot be freed. Two places to patch, neither clean.

In fact, readahead checks the count before starts the read. It should
prevent some of the problems but seems not enough.

And another idea, what if we just add dropbehind flag or rotate the
folio, if folio_try_lock failed in __try_to_reclaim_swap?