__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 Signed-off-by: Alexandre Ghiti --- 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