[PATCH v1 1/2] mm/swap: refault on swap-in, not in the swap cache allocator

From: Alexandre Ghiti

Date: Mon Aug 17 2026 - 11:01:10 EST


__swap_cache_alloc() calls workingset_refault() on every folio it
allocates into the swap cache. That is correct for a swap-in, but wrong
for a zswap writeback buffer, which is allocated through the same path
(swap_cache_alloc_folio()): the buffer is not a refault of anything, yet
it gets counted as one, inflating anon refault/activation.

Refaulting is a swap-in event, not an allocation event: move it out of
__swap_cache_alloc() into the swap-in callers.

The allocator has already put the folio on the LRU by the time the callers
refault, so workingset_refault() now has to activate an already-LRU folio:
folio_set_active() only sets the flag before LRU insertion and would be a
no-op here, so use folio_activate() when the folio is already on the LRU.
Existing callers pass not-yet-LRU folios and are unaffected.

Fixes: aae466b0052e ("mm/swap: implement workingset detection for anonymous LRU")
Signed-off-by: Nhat Pham <nphamcs@xxxxxxxxx>
Signed-off-by: Alexandre Ghiti <alex@xxxxxxxx>
---
mm/swap_state.c | 20 ++++++++++++++++++--
mm/workingset.c | 5 ++++-
2 files changed, 22 insertions(+), 3 deletions(-)

diff --git a/mm/swap_state.c b/mm/swap_state.c
index 9c3a5cf99778..0bb26d2ba2ba 100644
--- a/mm/swap_state.c
+++ b/mm/swap_state.c
@@ -477,8 +477,6 @@ static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,

/* memsw uncharges swap when folio is added to swap cache */
memcg1_swapin(folio);
- if (shadow)
- workingset_refault(folio, shadow);

node_stat_mod_folio(folio, NR_FILE_PAGES, nr_pages);
lruvec_stat_mod_folio(folio, NR_SWAPCACHE, nr_pages);
@@ -638,17 +636,26 @@ static struct folio *swap_cache_read_folio(swp_entry_t entry, gfp_t gfp,
struct swap_iocb **plug, bool readahead)
{
struct folio *folio;
+ void *shadow = NULL;

do {
folio = swap_cache_get_folio(entry);
if (folio)
return folio;
+ /*
+ * Capture the slot's shadow before the allocation overwrites it,
+ * so a fresh swap-in can be evaluated as a refault below.
+ */
+ shadow = swap_cache_get_shadow(entry);
folio = swap_cache_alloc_folio(entry, gfp, BIT(0), NULL, mpol, ilx);
} while (PTR_ERR(folio) == -EEXIST);

if (IS_ERR_OR_NULL(folio))
return NULL;

+ if (shadow)
+ workingset_refault(folio, shadow);
+
swap_read_folio(folio, plug);
if (readahead) {
folio_set_readahead(folio);
@@ -678,17 +685,26 @@ struct folio *swapin_sync(swp_entry_t entry, gfp_t gfp, unsigned long orders,
struct vm_fault *vmf, struct mempolicy *mpol, pgoff_t ilx)
{
struct folio *folio;
+ void *shadow = NULL;

do {
folio = swap_cache_get_folio(entry);
if (folio)
return folio;
+ /*
+ * Capture the slot's shadow before the allocation overwrites it,
+ * so a fresh swap-in can be evaluated as a refault below.
+ */
+ shadow = swap_cache_get_shadow(entry);
folio = swap_cache_alloc_folio(entry, gfp, orders, vmf, mpol, ilx);
} while (PTR_ERR(folio) == -EEXIST);

if (IS_ERR(folio))
return folio;

+ if (shadow)
+ workingset_refault(folio, shadow);
+
swap_read_folio(folio, NULL);
return folio;
}
diff --git a/mm/workingset.c b/mm/workingset.c
index f351798e723a..7c30cf091cbf 100644
--- a/mm/workingset.c
+++ b/mm/workingset.c
@@ -577,7 +577,10 @@ void workingset_refault(struct folio *folio, void *shadow)
if (!workingset_test_recent(shadow, file, &workingset, true))
goto out;

- folio_set_active(folio);
+ if (unlikely(folio_test_lru(folio)))
+ folio_activate(folio);
+ else
+ folio_set_active(folio);
workingset_age_nonresident(lruvec, nr);
mod_lruvec_state(lruvec, WORKINGSET_ACTIVATE_BASE + file, nr);

--
2.53.0-Meta