* [PATCH v4 0/3] mm: zswap: free cold writeback folios promptly
@ 2026-08-25 13:52 Alexandre Ghiti
2026-08-25 13:52 ` [PATCH v4 1/3] mm: swap: move LRU insertion out of the swap cache allocator Alexandre Ghiti
` (2 more replies)
0 siblings, 3 replies; 12+ messages in thread
From: Alexandre Ghiti @ 2026-08-25 13:52 UTC (permalink / raw)
To: Johannes Weiner, Yosry Ahmed, Nhat Pham, Andrew Morton, Chris Li,
Kairui Song
Cc: Kairui Song, Chengming Zhou, Matthew Wilcox (Oracle), Jan Kara,
Kemeng Shi, Baoquan He, Barry Song, Youngjun Park, Alexander Viro,
Christian Brauner, David Hildenbrand, Lorenzo Stoakes,
Michal Hocko, Axel Rasmussen, Qi Zheng, Shakeel Butt, Wei Xu,
Yuanchu Xie, Kunwu Chan, linux-mm, linux-kernel, linux-fsdevel,
Alexandre Ghiti
When zswap writes an entry back, it allocates an order-0 swap cache folio,
decompresses into it, and issues the write. The folio is cold by
construction, yet today it is left on the LRU for page reclaim to find and
free later. That wastes a reclaim scan and keeps cold memory resident
longer than necessary.
Rather than implement this in zswap, extend the existing dropbehind
mechanism to swap cache folios and have zswap opt into it (Yosry). A
PG_dropbehind folio is already dropped from its cache once writeback
completes instead of being left for reclaim; for a swap cache folio that
"drop" is removing it from the swap cache.
Patch 1 - move LRU insertion out of the swap cache allocator into its
callers, so zswap writeback can allocate off the LRU.
Patch 2 - drop dropbehind swap cache folios on writeback completion.
Patch 3 - zswap allocates its writeback folio off the LRU and marks it
dropbehind, opting into the mechanism above.
Note: patch 1 also appears as patch 1 of the zswap writeback refault
series [1]. It is the same patch. Both series need it and both are meant
to apply on their own, so it is posted in each; whichever lands first, the
other should drop it.
This version is based on Linus' tree rather than mm-unstable, because it
builds on Tal Zussman's BIO_COMPLETE_IN_TASK work merged in the 7.3 block
pull, which has not reached the mm tree yet. Thanks to Matthew and Barry for
pointing out this series!
v1: https://lore.kernel.org/linux-mm/20260718093723.153324-1-alex@ghiti.fr/
v2: https://lore.kernel.org/linux-mm/20260727143618.1582318-1-alex@ghiti.fr/
v3: https://lore.kernel.org/linux-mm/20260818163221.589352-1-alex@ghiti.fr/
[1] https://lore.kernel.org/linux-mm/20260821093606.2231216-1-alex@ghiti.fr/
Changes in v4:
- Rebase on BIO_COMPLETE_IN_TASK: set it on dropbehind swap writeback like
the file dropbehind paths do, and drop the folio directly from
folio_end_writeback(). This removes the per-CPU llist, the workqueue and
the reuse of folio->lru as the list node.
- zswap now drops its folio reference before starting writeback, so the
swap cache holds the only one and remove_mapping() sees the refcount it
expects. This fixes the drop on synchronous-IO devices and the race
Sashiko reported, where the drop could run before zswap released its
reference and fall back to the LRU. Verified on zram (the only
SWP_SYNCHRONOUS_IO backend I have): over ~6.7M writebacks per run, 99.999%
of the folios are dropped, and the refcount fallback fires 37-50 times.
- Use remove_mapping_reclaim() rather than adding a boolean argument to
remove_mapping(), which keeps the calling code readable (David). This also
leaves the existing remove_mapping() callers untouched.
- Patch 1: correct the changelog. The folio has to stay off the LRU because
folio_add_lru() leaves a reference in the per-CPU LRU batch, not because
of the free-time page-flag checks. Measured on zram, adding the folio to
the LRU instead drops the freed rate from 99.999% to 2.7%.
Changes in v3:
- Drop the synchronous-IO special case in zswap writeback (Yosry, Nhat).
- Use mem_cgroup_tryget()/mem_cgroup_put(): struct mem_cgroup is only
defined under CONFIG_MEMCG, so css_tryget()/css_put() failed to build
with CONFIG_MEMCG=n.
Changes in v2:
- Make swap dropbehind a generic core-mm mechanism that zswap opts into,
rather than a zswap-specific implementation (Yosry).
- Allocate off the LRU by moving folio_add_lru() out of the swap cache
allocator into its callers; rename it to __swap_cache_alloc_folio()
(Kairui).
- Skip the folio in the free path if it is still under writeback (Nhat).
Results
-------
Paired baseline vs series on async swap (NVMe). Each
workload runs confined to a memory cgroup (memory.max) small enough to force
zswap shrinker writeback.
Kernel build (defconfig, make -j4; memory.max = 600M):
metric baseline series delta
pgrotated 441028 2521 -99.4%
pgsteal_direct 3524343 2869004 -18.6%
pgscan_direct 8393791 7765008 -7.5%
zswpwb 705129 699019 -0.9%
build time (s) 1155 1114 -3.6%
Of the 699019 folios written back, 698990 (99.996%) were freed promptly on
writeback completion; only 28 fell back to reclaim.
MySQL/OLTP (sysbench, 10 tables x 1M rows, 512M buffer pool, 8 threads, 300s;
memory.max = 256M):
metric baseline series delta
transactions/s 153.87 163.30 +6.1%
p95 latency (ms) 157.42 145.82 -7.4%
avg latency (ms) 52.09 49.05 -5.9%
pgrotated 743738 22460 -97.0%
pgsteal_direct 6886490 5445278 -20.9%
pgscan_direct 13462510 10820730 -19.6%
Future work
-----------
Barry suggested extending this to MADV_PAGEOUT and general reclaim. I
prototyped dropbehind for all reclaimed swap folios and it regressed
sysbench OLTP throughput by ~15% on NVMe swap: dropping the swap cache
immediately turns cheap in-cache refaults into disk reads and collapses
swap readahead clustering. Neither blk-wbt, mq-deadline nor a PG_workingset
gate recovered it. MADV_PAGEOUT alone may still be worth it, since there
userspace has explicitly declared the range cold, but I have not measured
that case in isolation yet.
Alexandre Ghiti (3):
mm: swap: move LRU insertion out of the swap cache allocator
mm: swap: drop dropbehind swap cache folios on writeback completion
mm: zswap: drop cold writeback folios via swap dropbehind
include/linux/swap.h | 5 ++++
mm/filemap.c | 19 ++++++++++++++
mm/page_io.c | 7 +++++
mm/swap.h | 6 ++---
mm/swap_state.c | 61 ++++++++++++++++++++++++++++++++++++++------
mm/vmscan.c | 48 ++++++++++++++++++++++++++--------
mm/zswap.c | 22 +++++++++++++---
7 files changed, 143 insertions(+), 25 deletions(-)
base-commit: 55ab7e14222e5f0b0fd9f7711ca391d2924b35e3
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH v4 1/3] mm: swap: move LRU insertion out of the swap cache allocator
2026-08-25 13:52 [PATCH v4 0/3] mm: zswap: free cold writeback folios promptly Alexandre Ghiti
@ 2026-08-25 13:52 ` Alexandre Ghiti
2026-09-08 9:37 ` Kunwu Chan
2026-09-08 18:26 ` Nhat Pham
2026-08-25 13:52 ` [PATCH v4 2/3] mm: swap: drop dropbehind swap cache folios on writeback completion Alexandre Ghiti
2026-08-25 13:52 ` [PATCH v4 3/3] mm: zswap: drop cold writeback folios via swap dropbehind Alexandre Ghiti
2 siblings, 2 replies; 12+ messages in thread
From: Alexandre Ghiti @ 2026-08-25 13:52 UTC (permalink / raw)
To: Johannes Weiner, Yosry Ahmed, Nhat Pham, Andrew Morton, Chris Li,
Kairui Song
Cc: Kairui Song, Chengming Zhou, Matthew Wilcox (Oracle), Jan Kara,
Kemeng Shi, Baoquan He, Barry Song, Youngjun Park, Alexander Viro,
Christian Brauner, David Hildenbrand, Lorenzo Stoakes,
Michal Hocko, Axel Rasmussen, Qi Zheng, Shakeel Butt, Wei Xu,
Yuanchu Xie, Kunwu Chan, linux-mm, linux-kernel, linux-fsdevel,
Alexandre Ghiti
zswap writeback wants a swap cache folio it can free directly once
writeback completes, i.e. one that is not on the LRU (folio_add_lru()
stages the folio in a per-CPU batch that holds a reference until it is
drained, which keeps remove_mapping() from freeing the folio on
synchronous devices, and likely on asynchronous ones too).
So defer the LRU addition to the callers of __swap_cache_alloc_folio(),
no functional change intended.
Suggested-by: Kairui Song <kasong@tencent.com>
Signed-off-by: Alexandre Ghiti <alex@ghiti.fr>
---
mm/swap.h | 6 +++---
mm/swap_state.c | 20 ++++++++++++--------
mm/zswap.c | 5 +++--
3 files changed, 18 insertions(+), 13 deletions(-)
diff --git a/mm/swap.h b/mm/swap.h
index 77d2d14eda42..fc44daae1de1 100644
--- a/mm/swap.h
+++ b/mm/swap.h
@@ -304,9 +304,9 @@ bool swap_cache_has_folio(swp_entry_t entry);
struct folio *swap_cache_get_folio(swp_entry_t entry);
void *swap_cache_get_shadow(swp_entry_t entry);
void swap_cache_del_folio(struct folio *folio);
-struct folio *swap_cache_alloc_folio(swp_entry_t target_entry, gfp_t gfp_mask,
- unsigned long orders, struct vm_fault *vmf,
- struct mempolicy *mpol, pgoff_t ilx);
+struct folio *__swap_cache_alloc_folio(swp_entry_t target_entry, gfp_t gfp_mask,
+ unsigned long orders, struct vm_fault *vmf,
+ struct mempolicy *mpol, pgoff_t ilx);
/* Below helpers require the caller to lock and pass in the swap cluster. */
void __swap_cache_add_folio(struct swap_cluster_info *ci,
struct folio *folio, swp_entry_t entry);
diff --git a/mm/swap_state.c b/mm/swap_state.c
index 727a17ee7821..07418fc94f00 100644
--- a/mm/swap_state.c
+++ b/mm/swap_state.c
@@ -483,13 +483,11 @@ static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,
node_stat_mod_folio(folio, NR_FILE_PAGES, nr_pages);
lruvec_stat_mod_folio(folio, NR_SWAPCACHE, nr_pages);
- /* Caller will initiate read into locked new_folio */
- folio_add_lru(folio);
return folio;
}
/**
- * swap_cache_alloc_folio - Allocate folio for swapped out slot in swap cache.
+ * __swap_cache_alloc_folio - Allocate folio for swapped out slot in swap cache.
* @targ_entry: swap entry indicating the target slot
* @gfp: memory allocation flags
* @orders: allocation orders, must be non zero
@@ -501,13 +499,17 @@ static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,
* doing IO (e.g. swap in or zswap writeback). The swap slot indicated by
* @targ_entry must have a non-zero swap count (swapped out).
*
+ * The returned folio is locked and is NOT on the LRU. The caller must either
+ * add it to the LRU with folio_add_lru() so page reclaim can find it, or free
+ * it directly once done; a folio left off the LRU is unreclaimable and leaks.
+ *
* Context: Caller must protect the swap device with reference count or locks.
* Return: Returns the folio if allocation succeeded and folio is in the swap
* cache. Returns error code if failed due to race, OOM or invalid arguments.
*/
-struct folio *swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,
- unsigned long orders, struct vm_fault *vmf,
- struct mempolicy *mpol, pgoff_t ilx)
+struct folio *__swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,
+ unsigned long orders, struct vm_fault *vmf,
+ struct mempolicy *mpol, pgoff_t ilx)
{
int order, err;
struct folio *ret;
@@ -643,12 +645,13 @@ static struct folio *swap_cache_read_folio(swp_entry_t entry, gfp_t gfp,
folio = swap_cache_get_folio(entry);
if (folio)
return folio;
- folio = swap_cache_alloc_folio(entry, gfp, BIT(0), NULL, mpol, ilx);
+ 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;
+ folio_add_lru(folio);
swap_read_folio(folio, plug);
if (readahead) {
folio_set_readahead(folio);
@@ -683,12 +686,13 @@ struct folio *swapin_sync(swp_entry_t entry, gfp_t gfp, unsigned long orders,
folio = swap_cache_get_folio(entry);
if (folio)
return folio;
- folio = swap_cache_alloc_folio(entry, gfp, orders, vmf, mpol, ilx);
+ folio = __swap_cache_alloc_folio(entry, gfp, orders, vmf, mpol, ilx);
} while (PTR_ERR(folio) == -EEXIST);
if (IS_ERR(folio))
return folio;
+ folio_add_lru(folio);
swap_read_folio(folio, NULL);
return folio;
}
diff --git a/mm/zswap.c b/mm/zswap.c
index 761cd699e0a3..8163e6c5f76c 100644
--- a/mm/zswap.c
+++ b/mm/zswap.c
@@ -1000,8 +1000,8 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
return -EEXIST;
mpol = get_task_policy(current);
- folio = swap_cache_alloc_folio(swpentry, GFP_KERNEL, BIT(0), NULL, mpol,
- NO_INTERLEAVE_INDEX);
+ folio = __swap_cache_alloc_folio(swpentry, GFP_KERNEL, BIT(0), NULL, mpol,
+ NO_INTERLEAVE_INDEX);
put_swap_device(si);
/*
@@ -1013,6 +1013,7 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
*/
if (IS_ERR(folio))
return PTR_ERR(folio);
+ folio_add_lru(folio);
/*
* folio is locked, and the swapcache is now secured against
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH v4 1/3] mm: swap: move LRU insertion out of the swap cache allocator
2026-08-25 13:52 ` [PATCH v4 1/3] mm: swap: move LRU insertion out of the swap cache allocator Alexandre Ghiti
@ 2026-09-08 9:37 ` Kunwu Chan
2026-09-08 18:26 ` Nhat Pham
1 sibling, 0 replies; 12+ messages in thread
From: Kunwu Chan @ 2026-09-08 9:37 UTC (permalink / raw)
To: Alexandre Ghiti
Cc: Kunwu Chan, Johannes Weiner, Yosry Ahmed, Nhat Pham,
Andrew Morton, Chris Li, Kairui Song, Kairui Song, Chengming Zhou,
Matthew Wilcox (Oracle), Jan Kara, Kemeng Shi, Baoquan He,
Barry Song, Youngjun Park, Alexander Viro, Christian Brauner,
David Hildenbrand, Lorenzo Stoakes, Michal Hocko, Axel Rasmussen,
Qi Zheng, Shakeel Butt, Wei Xu, Yuanchu Xie, linux-mm,
linux-kernel, linux-fsdevel
On Tue, 25 Aug 2026 15:52:05 +0200 Alexandre Ghiti <alex@ghiti.fr> wrote:
> zswap writeback wants a swap cache folio it can free directly once
> writeback completes, i.e. one that is not on the LRU (folio_add_lru()
> stages the folio in a per-CPU batch that holds a reference until it is
> drained, which keeps remove_mapping() from freeing the folio on
> synchronous devices, and likely on asynchronous ones too).
>
> So defer the LRU addition to the callers of __swap_cache_alloc_folio(),
> no functional change intended.
>
> Suggested-by: Kairui Song <kasong@tencent.com>
> Signed-off-by: Alexandre Ghiti <alex@ghiti.fr>
> ---
> mm/swap.h | 6 +++---
> mm/swap_state.c | 20 ++++++++++++--------
> mm/zswap.c | 5 +++--
> 3 files changed, 18 insertions(+), 13 deletions(-)
>
> diff --git a/mm/swap.h b/mm/swap.h
> index 77d2d14eda42..fc44daae1de1 100644
> --- a/mm/swap.h
> +++ b/mm/swap.h
> @@ -304,9 +304,9 @@ bool swap_cache_has_folio(swp_entry_t entry);
> struct folio *swap_cache_get_folio(swp_entry_t entry);
> void *swap_cache_get_shadow(swp_entry_t entry);
> void swap_cache_del_folio(struct folio *folio);
> -struct folio *swap_cache_alloc_folio(swp_entry_t target_entry, gfp_t gfp_mask,
> - unsigned long orders, struct vm_fault *vmf,
> - struct mempolicy *mpol, pgoff_t ilx);
> +struct folio *__swap_cache_alloc_folio(swp_entry_t target_entry, gfp_t gfp_mask,
> + unsigned long orders, struct vm_fault *vmf,
> + struct mempolicy *mpol, pgoff_t ilx);
> /* Below helpers require the caller to lock and pass in the swap cluster. */
> void __swap_cache_add_folio(struct swap_cluster_info *ci,
> struct folio *folio, swp_entry_t entry);
> diff --git a/mm/swap_state.c b/mm/swap_state.c
> index 727a17ee7821..07418fc94f00 100644
> --- a/mm/swap_state.c
> +++ b/mm/swap_state.c
> @@ -483,13 +483,11 @@ static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,
> node_stat_mod_folio(folio, NR_FILE_PAGES, nr_pages);
> lruvec_stat_mod_folio(folio, NR_SWAPCACHE, nr_pages);
>
> - /* Caller will initiate read into locked new_folio */
> - folio_add_lru(folio);
> return folio;
> }
>
> /**
> - * swap_cache_alloc_folio - Allocate folio for swapped out slot in swap cache.
> + * __swap_cache_alloc_folio - Allocate folio for swapped out slot in swap cache.
> * @targ_entry: swap entry indicating the target slot
> * @gfp: memory allocation flags
> * @orders: allocation orders, must be non zero
> @@ -501,13 +499,17 @@ static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,
> * doing IO (e.g. swap in or zswap writeback). The swap slot indicated by
> * @targ_entry must have a non-zero swap count (swapped out).
> *
> + * The returned folio is locked and is NOT on the LRU. The caller must either
> + * add it to the LRU with folio_add_lru() so page reclaim can find it, or free
> + * it directly once done; a folio left off the LRU is unreclaimable and leaks.
> + *
> * Context: Caller must protect the swap device with reference count or locks.
> * Return: Returns the folio if allocation succeeded and folio is in the swap
> * cache. Returns error code if failed due to race, OOM or invalid arguments.
> */
> -struct folio *swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,
> - unsigned long orders, struct vm_fault *vmf,
> - struct mempolicy *mpol, pgoff_t ilx)
> +struct folio *__swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,
> + unsigned long orders, struct vm_fault *vmf,
> + struct mempolicy *mpol, pgoff_t ilx)
> {
> int order, err;
> struct folio *ret;
> @@ -643,12 +645,13 @@ static struct folio *swap_cache_read_folio(swp_entry_t entry, gfp_t gfp,
> folio = swap_cache_get_folio(entry);
> if (folio)
> return folio;
> - folio = swap_cache_alloc_folio(entry, gfp, BIT(0), NULL, mpol, ilx);
> + 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;
>
> + folio_add_lru(folio);
> swap_read_folio(folio, plug);
> if (readahead) {
> folio_set_readahead(folio);
> @@ -683,12 +686,13 @@ struct folio *swapin_sync(swp_entry_t entry, gfp_t gfp, unsigned long orders,
> folio = swap_cache_get_folio(entry);
> if (folio)
> return folio;
> - folio = swap_cache_alloc_folio(entry, gfp, orders, vmf, mpol, ilx);
> + folio = __swap_cache_alloc_folio(entry, gfp, orders, vmf, mpol, ilx);
> } while (PTR_ERR(folio) == -EEXIST);
>
> if (IS_ERR(folio))
> return folio;
>
> + folio_add_lru(folio);
> swap_read_folio(folio, NULL);
> return folio;
> }
> diff --git a/mm/zswap.c b/mm/zswap.c
> index 761cd699e0a3..8163e6c5f76c 100644
> --- a/mm/zswap.c
> +++ b/mm/zswap.c
> @@ -1000,8 +1000,8 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
> return -EEXIST;
>
> mpol = get_task_policy(current);
> - folio = swap_cache_alloc_folio(swpentry, GFP_KERNEL, BIT(0), NULL, mpol,
> - NO_INTERLEAVE_INDEX);
> + folio = __swap_cache_alloc_folio(swpentry, GFP_KERNEL, BIT(0), NULL, mpol,
> + NO_INTERLEAVE_INDEX);
> put_swap_device(si);
>
> /*
> @@ -1013,6 +1013,7 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
> */
> if (IS_ERR(folio))
> return PTR_ERR(folio);
> + folio_add_lru(folio);
>
> /*
> * folio is locked, and the swapcache is now secured against
> --
> 2.53.0-Meta
>
>
I checked the callers of __swap_cache_alloc_folio(). The regular
swap-in paths add the folio to the LRU after allocation, while
the zswap writeback path keeps it off-LRU for the dropbehind
handling in patch 3.
The new allocator contract is preserved by all callers.
swapfile.c still references the old name in a comment — minor
consistency nit.
Reviewed-by: Kunwu Chan <kunwu.chan@gmail.com>
Thanks,
KunWu
^ permalink raw reply [flat|nested] 12+ messages in thread* Re: [PATCH v4 1/3] mm: swap: move LRU insertion out of the swap cache allocator
2026-08-25 13:52 ` [PATCH v4 1/3] mm: swap: move LRU insertion out of the swap cache allocator Alexandre Ghiti
2026-09-08 9:37 ` Kunwu Chan
@ 2026-09-08 18:26 ` Nhat Pham
1 sibling, 0 replies; 12+ messages in thread
From: Nhat Pham @ 2026-09-08 18:26 UTC (permalink / raw)
To: Alexandre Ghiti
Cc: Johannes Weiner, Yosry Ahmed, Andrew Morton, Chris Li,
Kairui Song, Kairui Song, Chengming Zhou, Matthew Wilcox (Oracle),
Jan Kara, Kemeng Shi, Baoquan He, Barry Song, Youngjun Park,
Alexander Viro, Christian Brauner, David Hildenbrand,
Lorenzo Stoakes, Michal Hocko, Axel Rasmussen, Qi Zheng,
Shakeel Butt, Wei Xu, Yuanchu Xie, Kunwu Chan, linux-mm,
linux-kernel, linux-fsdevel
On Tue, Aug 25, 2026 at 6:53 AM Alexandre Ghiti <alex@ghiti.fr> wrote:
>
> zswap writeback wants a swap cache folio it can free directly once
> writeback completes, i.e. one that is not on the LRU (folio_add_lru()
> stages the folio in a per-CPU batch that holds a reference until it is
> drained, which keeps remove_mapping() from freeing the folio on
> synchronous devices, and likely on asynchronous ones too).
>
> So defer the LRU addition to the callers of __swap_cache_alloc_folio(),
> no functional change intended.
>
> Suggested-by: Kairui Song <kasong@tencent.com>
> Signed-off-by: Alexandre Ghiti <alex@ghiti.fr>
LGTM.
Reviewed-by: Nhat Pham <nphamcs@gmail.com>
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v4 2/3] mm: swap: drop dropbehind swap cache folios on writeback completion
2026-08-25 13:52 [PATCH v4 0/3] mm: zswap: free cold writeback folios promptly Alexandre Ghiti
2026-08-25 13:52 ` [PATCH v4 1/3] mm: swap: move LRU insertion out of the swap cache allocator Alexandre Ghiti
@ 2026-08-25 13:52 ` Alexandre Ghiti
2026-09-08 9:58 ` Kunwu Chan
2026-09-08 18:39 ` Nhat Pham
2026-08-25 13:52 ` [PATCH v4 3/3] mm: zswap: drop cold writeback folios via swap dropbehind Alexandre Ghiti
2 siblings, 2 replies; 12+ messages in thread
From: Alexandre Ghiti @ 2026-08-25 13:52 UTC (permalink / raw)
To: Johannes Weiner, Yosry Ahmed, Nhat Pham, Andrew Morton, Chris Li,
Kairui Song
Cc: Kairui Song, Chengming Zhou, Matthew Wilcox (Oracle), Jan Kara,
Kemeng Shi, Baoquan He, Barry Song, Youngjun Park, Alexander Viro,
Christian Brauner, David Hildenbrand, Lorenzo Stoakes,
Michal Hocko, Axel Rasmussen, Qi Zheng, Shakeel Butt, Wei Xu,
Yuanchu Xie, Kunwu Chan, linux-mm, linux-kernel, linux-fsdevel,
Alexandre Ghiti
A PG_dropbehind folio is dropped from its cache once writeback completes
rather than left for reclaim to find later; this is implemented for file
folios in folio_end_dropbehind(). Extend it to swap cache folios.
The drop takes the folio and swap cluster locks and may sleep, so it
cannot run in interrupt context. Set BIO_COMPLETE_IN_TASK on the write,
as the file dropbehind paths do, and drop the folio directly from
folio_end_writeback().
Suggested-by: Yosry Ahmed <yosry@kernel.org>
Suggested-by: Johannes Weiner <hannes@cmpxchg.org>
Suggested-by: Nhat Pham <nphamcs@gmail.com>
Signed-off-by: Alexandre Ghiti <alex@ghiti.fr>
---
include/linux/swap.h | 5 +++++
mm/filemap.c | 19 ++++++++++++++++++
mm/page_io.c | 7 +++++++
mm/swap_state.c | 41 +++++++++++++++++++++++++++++++++++++
mm/vmscan.c | 48 +++++++++++++++++++++++++++++++++++---------
5 files changed, 110 insertions(+), 10 deletions(-)
diff --git a/include/linux/swap.h b/include/linux/swap.h
index 8f0f68e245ba..29ec60dcae21 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -374,6 +374,8 @@ extern unsigned long mem_cgroup_shrink_node(struct mem_cgroup *mem,
extern unsigned long shrink_all_memory(unsigned long nr_pages);
extern int vm_swappiness;
long remove_mapping(struct address_space *mapping, struct folio *folio);
+long remove_mapping_reclaim(struct address_space *mapping, struct folio *folio,
+ struct mem_cgroup *target_memcg);
#if defined(CONFIG_SYSFS) && defined(CONFIG_NUMA)
extern int reclaim_register_node(struct node *node);
@@ -465,6 +467,8 @@ void swap_put_entries_direct(swp_entry_t entry, int nr);
*/
bool folio_free_swap(struct folio *folio);
+void swap_writeback_dropbehind_folio(struct folio *folio);
+
/* Allocate / free (hibernation) exclusive entries */
swp_entry_t swap_alloc_hibernation_slot(int type);
void swap_free_hibernation_slot(swp_entry_t entry);
@@ -475,6 +479,7 @@ static inline void put_swap_device(struct swap_info_struct *si)
}
#else /* CONFIG_SWAP */
+static inline void swap_writeback_dropbehind_folio(struct folio *folio) {}
static inline struct swap_info_struct *get_swap_device(swp_entry_t entry)
{
return NULL;
diff --git a/mm/filemap.c b/mm/filemap.c
index d721986d5f46..040c97a121de 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -1686,6 +1686,8 @@ EXPORT_SYMBOL_GPL(folio_end_writeback_no_dropbehind);
*/
void folio_end_writeback(struct folio *folio)
{
+ bool swap_dropbehind;
+
VM_BUG_ON_FOLIO(!folio_test_writeback(folio), folio);
/*
@@ -1695,7 +1697,24 @@ void folio_end_writeback(struct folio *folio)
* reused before the folio_wake_bit().
*/
folio_get(folio);
+
+ /*
+ * Sample this before folio_end_writeback_no_dropbehind() clears
+ * PG_writeback: until then a racing swapin cannot remove the folio from
+ * the swap cache. Afterwards it can, and the drop below then finds a
+ * non-swapcache folio and puts it back on the LRU instead. The
+ * reference taken above keeps the folio alive across that window.
+ */
+ swap_dropbehind = folio_test_swapcache(folio) &&
+ folio_test_dropbehind(folio);
+
folio_end_writeback_no_dropbehind(folio);
+
+ if (swap_dropbehind) {
+ swap_writeback_dropbehind_folio(folio);
+ return;
+ }
+
folio_end_dropbehind(folio);
folio_put(folio);
}
diff --git a/mm/page_io.c b/mm/page_io.c
index b23f494fcc83..586c79c3bb3d 100644
--- a/mm/page_io.c
+++ b/mm/page_io.c
@@ -456,6 +456,13 @@ static void swap_writepage_bdev_async(struct folio *folio,
bio->bi_end_io = end_swap_bio_write;
bio_add_folio_nofail(bio, folio, folio_size(folio), 0);
+ /*
+ * Dropping the folio from the swap cache takes sleeping locks, so the
+ * completion must not run in interrupt context.
+ */
+ if (folio_test_dropbehind(folio))
+ bio_set_flag(bio, BIO_COMPLETE_IN_TASK);
+
bio_associate_blkg_from_page(bio, folio);
count_swpout_vm_event(folio);
folio_start_writeback(folio);
diff --git a/mm/swap_state.c b/mm/swap_state.c
index 07418fc94f00..231fa87cbbe0 100644
--- a/mm/swap_state.c
+++ b/mm/swap_state.c
@@ -537,6 +537,47 @@ struct folio *__swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,
return ret;
}
+/**
+ * swap_writeback_dropbehind_folio - drop a dropbehind swap cache folio
+ * @folio: the off-LRU folio whose writeback has completed
+ *
+ * Context: task context, with the reference taken by folio_end_writeback()
+ * donated to us.
+ */
+void swap_writeback_dropbehind_folio(struct folio *folio)
+{
+ struct mem_cgroup *memcg;
+
+ folio_lock(folio);
+
+ /* The folio was allocated off the LRU and nothing re-adds it here. */
+ VM_WARN_ON_ONCE_FOLIO(folio_test_lru(folio), folio);
+
+ rcu_read_lock();
+ memcg = folio_memcg(folio);
+ if (!mem_cgroup_tryget(memcg))
+ memcg = NULL;
+ rcu_read_unlock();
+
+ /*
+ * Gate remove_mapping_reclaim() on folio_test_swapcache(): a racing
+ * swapin may have freed the swap slot (folio_free_swap()) and dropped the
+ * folio from the cache, and it must not run on a non-swapcache folio (it
+ * would trip __remove_mapping()'s mapping == folio_mapping() check).
+ */
+ if (!folio_test_swapcache(folio) || folio_test_writeback(folio) ||
+ !remove_mapping_reclaim(swap_address_space(folio->swap), folio, memcg)) {
+ /* Raced: the folio is now owned by the swapin; put it back. */
+ folio_clear_dropbehind(folio);
+ folio_add_lru(folio);
+ }
+
+ mem_cgroup_put(memcg);
+
+ folio_unlock(folio);
+ folio_put(folio);
+}
+
/*
* If we are the only user, then try to free up the swap cache.
*
diff --git a/mm/vmscan.c b/mm/vmscan.c
index 848bd3e5eee2..4cc3a3ed6db6 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -780,6 +780,22 @@ static int __remove_mapping(struct address_space *mapping, struct folio *folio,
return 0;
}
+static long __remove_mapping_unfreeze(struct address_space *mapping,
+ struct folio *folio, bool reclaimed,
+ struct mem_cgroup *target_memcg)
+{
+ if (__remove_mapping(mapping, folio, reclaimed, target_memcg)) {
+ /*
+ * Unfreezing the refcount with 1 effectively
+ * drops the pagecache ref for us without requiring another
+ * atomic operation.
+ */
+ folio_ref_unfreeze(folio, 1);
+ return folio_nr_pages(folio);
+ }
+ return 0;
+}
+
/**
* remove_mapping() - Attempt to remove a folio from its mapping.
* @mapping: The address space.
@@ -794,16 +810,28 @@ static int __remove_mapping(struct address_space *mapping, struct folio *folio,
*/
long remove_mapping(struct address_space *mapping, struct folio *folio)
{
- if (__remove_mapping(mapping, folio, false, NULL)) {
- /*
- * Unfreezing the refcount with 1 effectively
- * drops the pagecache ref for us without requiring another
- * atomic operation.
- */
- folio_ref_unfreeze(folio, 1);
- return folio_nr_pages(folio);
- }
- return 0;
+ return __remove_mapping_unfreeze(mapping, folio, false, NULL);
+}
+
+/**
+ * remove_mapping_reclaim() - Remove a folio from its mapping, as reclaim does.
+ * @mapping: The address space.
+ * @folio: The folio to remove.
+ * @target_memcg: The memcg to charge the eviction shadow to; the caller must
+ * keep it alive across the call.
+ *
+ * Like remove_mapping(), but stores a workingset eviction shadow the way page
+ * reclaim does, so that a later refault can be detected and the folio
+ * re-activated.
+ * Return: The number of pages removed from the mapping. 0 if the folio
+ * could not be removed.
+ * Context: The caller should have a single refcount on the folio and
+ * hold its lock.
+ */
+long remove_mapping_reclaim(struct address_space *mapping, struct folio *folio,
+ struct mem_cgroup *target_memcg)
+{
+ return __remove_mapping_unfreeze(mapping, folio, true, target_memcg);
}
/**
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH v4 2/3] mm: swap: drop dropbehind swap cache folios on writeback completion
2026-08-25 13:52 ` [PATCH v4 2/3] mm: swap: drop dropbehind swap cache folios on writeback completion Alexandre Ghiti
@ 2026-09-08 9:58 ` Kunwu Chan
2026-09-08 18:39 ` Nhat Pham
1 sibling, 0 replies; 12+ messages in thread
From: Kunwu Chan @ 2026-09-08 9:58 UTC (permalink / raw)
To: Alexandre Ghiti
Cc: Kunwu Chan, Johannes Weiner, Yosry Ahmed, Nhat Pham,
Andrew Morton, Chris Li, Kairui Song, Kairui Song, Chengming Zhou,
Matthew Wilcox (Oracle), Jan Kara, Kemeng Shi, Baoquan He,
Barry Song, Youngjun Park, Alexander Viro, Christian Brauner,
David Hildenbrand, Lorenzo Stoakes, Michal Hocko, Axel Rasmussen,
Qi Zheng, Shakeel Butt, Wei Xu, Yuanchu Xie, linux-mm,
linux-kernel, linux-fsdevel
On Tue, 25 Aug 2026 15:52:06 +0200 Alexandre Ghiti <alex@ghiti.fr> wrote:
> A PG_dropbehind folio is dropped from its cache once writeback completes
> rather than left for reclaim to find later; this is implemented for file
> folios in folio_end_dropbehind(). Extend it to swap cache folios.
>
> The drop takes the folio and swap cluster locks and may sleep, so it
> cannot run in interrupt context. Set BIO_COMPLETE_IN_TASK on the write,
> as the file dropbehind paths do, and drop the folio directly from
> folio_end_writeback().
>
> Suggested-by: Yosry Ahmed <yosry@kernel.org>
> Suggested-by: Johannes Weiner <hannes@cmpxchg.org>
> Suggested-by: Nhat Pham <nphamcs@gmail.com>
> Signed-off-by: Alexandre Ghiti <alex@ghiti.fr>
> ---
> include/linux/swap.h | 5 +++++
> mm/filemap.c | 19 ++++++++++++++++++
> mm/page_io.c | 7 +++++++
> mm/swap_state.c | 41 +++++++++++++++++++++++++++++++++++++
> mm/vmscan.c | 48 +++++++++++++++++++++++++++++++++++---------
> 5 files changed, 110 insertions(+), 10 deletions(-)
>
> diff --git a/include/linux/swap.h b/include/linux/swap.h
> index 8f0f68e245ba..29ec60dcae21 100644
> --- a/include/linux/swap.h
> +++ b/include/linux/swap.h
> @@ -374,6 +374,8 @@ extern unsigned long mem_cgroup_shrink_node(struct mem_cgroup *mem,
> extern unsigned long shrink_all_memory(unsigned long nr_pages);
> extern int vm_swappiness;
> long remove_mapping(struct address_space *mapping, struct folio *folio);
> +long remove_mapping_reclaim(struct address_space *mapping, struct folio *folio,
> + struct mem_cgroup *target_memcg);
>
> #if defined(CONFIG_SYSFS) && defined(CONFIG_NUMA)
> extern int reclaim_register_node(struct node *node);
> @@ -465,6 +467,8 @@ void swap_put_entries_direct(swp_entry_t entry, int nr);
> */
> bool folio_free_swap(struct folio *folio);
>
> +void swap_writeback_dropbehind_folio(struct folio *folio);
> +
> /* Allocate / free (hibernation) exclusive entries */
> swp_entry_t swap_alloc_hibernation_slot(int type);
> void swap_free_hibernation_slot(swp_entry_t entry);
> @@ -475,6 +479,7 @@ static inline void put_swap_device(struct swap_info_struct *si)
> }
>
> #else /* CONFIG_SWAP */
> +static inline void swap_writeback_dropbehind_folio(struct folio *folio) {}
> static inline struct swap_info_struct *get_swap_device(swp_entry_t entry)
> {
> return NULL;
> diff --git a/mm/filemap.c b/mm/filemap.c
> index d721986d5f46..040c97a121de 100644
> --- a/mm/filemap.c
> +++ b/mm/filemap.c
> @@ -1686,6 +1686,8 @@ EXPORT_SYMBOL_GPL(folio_end_writeback_no_dropbehind);
> */
> void folio_end_writeback(struct folio *folio)
> {
> + bool swap_dropbehind;
> +
> VM_BUG_ON_FOLIO(!folio_test_writeback(folio), folio);
>
> /*
> @@ -1695,7 +1697,24 @@ void folio_end_writeback(struct folio *folio)
> * reused before the folio_wake_bit().
> */
> folio_get(folio);
> +
> + /*
> + * Sample this before folio_end_writeback_no_dropbehind() clears
> + * PG_writeback: until then a racing swapin cannot remove the folio from
> + * the swap cache. Afterwards it can, and the drop below then finds a
> + * non-swapcache folio and puts it back on the LRU instead. The
> + * reference taken above keeps the folio alive across that window.
> + */
> + swap_dropbehind = folio_test_swapcache(folio) &&
> + folio_test_dropbehind(folio);
> +
> folio_end_writeback_no_dropbehind(folio);
> +
> + if (swap_dropbehind) {
> + swap_writeback_dropbehind_folio(folio);
> + return;
I checked the refcount handoff from folio_end_writeback() to
swap_writeback_dropbehind_folio(): the extra reference provides the
expected caller reference for remove_mapping_reclaim(), giving the
expected 1 + folio_nr_pages(folio) count for folio_ref_freeze().
The swapcache/dropbehind state is sampled before clearing PG_writeback,
and the extra reference keeps the folio alive across a racing swapin.
Reviewed-by: Kunwu Chan <kunwu.chan@gmail.com>
Thanks,
KunWu
> + }
> +
> folio_end_dropbehind(folio);
> folio_put(folio);
> }
> diff --git a/mm/page_io.c b/mm/page_io.c
> index b23f494fcc83..586c79c3bb3d 100644
> --- a/mm/page_io.c
> +++ b/mm/page_io.c
> @@ -456,6 +456,13 @@ static void swap_writepage_bdev_async(struct folio *folio,
> bio->bi_end_io = end_swap_bio_write;
> bio_add_folio_nofail(bio, folio, folio_size(folio), 0);
>
> + /*
> + * Dropping the folio from the swap cache takes sleeping locks, so the
> + * completion must not run in interrupt context.
> + */
> + if (folio_test_dropbehind(folio))
> + bio_set_flag(bio, BIO_COMPLETE_IN_TASK);
> +
> bio_associate_blkg_from_page(bio, folio);
> count_swpout_vm_event(folio);
> folio_start_writeback(folio);
> diff --git a/mm/swap_state.c b/mm/swap_state.c
> index 07418fc94f00..231fa87cbbe0 100644
> --- a/mm/swap_state.c
> +++ b/mm/swap_state.c
> @@ -537,6 +537,47 @@ struct folio *__swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,
> return ret;
> }
>
> +/**
> + * swap_writeback_dropbehind_folio - drop a dropbehind swap cache folio
> + * @folio: the off-LRU folio whose writeback has completed
> + *
> + * Context: task context, with the reference taken by folio_end_writeback()
> + * donated to us.
> + */
> +void swap_writeback_dropbehind_folio(struct folio *folio)
> +{
> + struct mem_cgroup *memcg;
> +
> + folio_lock(folio);
> +
> + /* The folio was allocated off the LRU and nothing re-adds it here. */
> + VM_WARN_ON_ONCE_FOLIO(folio_test_lru(folio), folio);
> +
> + rcu_read_lock();
> + memcg = folio_memcg(folio);
> + if (!mem_cgroup_tryget(memcg))
> + memcg = NULL;
> + rcu_read_unlock();
> +
> + /*
> + * Gate remove_mapping_reclaim() on folio_test_swapcache(): a racing
> + * swapin may have freed the swap slot (folio_free_swap()) and dropped the
> + * folio from the cache, and it must not run on a non-swapcache folio (it
> + * would trip __remove_mapping()'s mapping == folio_mapping() check).
> + */
> + if (!folio_test_swapcache(folio) || folio_test_writeback(folio) ||
> + !remove_mapping_reclaim(swap_address_space(folio->swap), folio, memcg)) {
> + /* Raced: the folio is now owned by the swapin; put it back. */
> + folio_clear_dropbehind(folio);
> + folio_add_lru(folio);
> + }
> +
> + mem_cgroup_put(memcg);
> +
> + folio_unlock(folio);
> + folio_put(folio);
> +}
> +
> /*
> * If we are the only user, then try to free up the swap cache.
> *
> diff --git a/mm/vmscan.c b/mm/vmscan.c
> index 848bd3e5eee2..4cc3a3ed6db6 100644
> --- a/mm/vmscan.c
> +++ b/mm/vmscan.c
> @@ -780,6 +780,22 @@ static int __remove_mapping(struct address_space *mapping, struct folio *folio,
> return 0;
> }
>
> +static long __remove_mapping_unfreeze(struct address_space *mapping,
> + struct folio *folio, bool reclaimed,
> + struct mem_cgroup *target_memcg)
> +{
> + if (__remove_mapping(mapping, folio, reclaimed, target_memcg)) {
> + /*
> + * Unfreezing the refcount with 1 effectively
> + * drops the pagecache ref for us without requiring another
> + * atomic operation.
> + */
> + folio_ref_unfreeze(folio, 1);
> + return folio_nr_pages(folio);
> + }
> + return 0;
> +}
> +
> /**
> * remove_mapping() - Attempt to remove a folio from its mapping.
> * @mapping: The address space.
> @@ -794,16 +810,28 @@ static int __remove_mapping(struct address_space *mapping, struct folio *folio,
> */
> long remove_mapping(struct address_space *mapping, struct folio *folio)
> {
> - if (__remove_mapping(mapping, folio, false, NULL)) {
> - /*
> - * Unfreezing the refcount with 1 effectively
> - * drops the pagecache ref for us without requiring another
> - * atomic operation.
> - */
> - folio_ref_unfreeze(folio, 1);
> - return folio_nr_pages(folio);
> - }
> - return 0;
> + return __remove_mapping_unfreeze(mapping, folio, false, NULL);
> +}
> +
> +/**
> + * remove_mapping_reclaim() - Remove a folio from its mapping, as reclaim does.
> + * @mapping: The address space.
> + * @folio: The folio to remove.
> + * @target_memcg: The memcg to charge the eviction shadow to; the caller must
> + * keep it alive across the call.
> + *
> + * Like remove_mapping(), but stores a workingset eviction shadow the way page
> + * reclaim does, so that a later refault can be detected and the folio
> + * re-activated.
> + * Return: The number of pages removed from the mapping. 0 if the folio
> + * could not be removed.
> + * Context: The caller should have a single refcount on the folio and
> + * hold its lock.
> + */
> +long remove_mapping_reclaim(struct address_space *mapping, struct folio *folio,
> + struct mem_cgroup *target_memcg)
> +{
> + return __remove_mapping_unfreeze(mapping, folio, true, target_memcg);
> }
>
> /**
> --
> 2.53.0-Meta
>
>
^ permalink raw reply [flat|nested] 12+ messages in thread* Re: [PATCH v4 2/3] mm: swap: drop dropbehind swap cache folios on writeback completion
2026-08-25 13:52 ` [PATCH v4 2/3] mm: swap: drop dropbehind swap cache folios on writeback completion Alexandre Ghiti
2026-09-08 9:58 ` Kunwu Chan
@ 2026-09-08 18:39 ` Nhat Pham
1 sibling, 0 replies; 12+ messages in thread
From: Nhat Pham @ 2026-09-08 18:39 UTC (permalink / raw)
To: Alexandre Ghiti
Cc: Johannes Weiner, Yosry Ahmed, Andrew Morton, Chris Li,
Kairui Song, Kairui Song, Chengming Zhou, Matthew Wilcox (Oracle),
Jan Kara, Kemeng Shi, Baoquan He, Barry Song, Youngjun Park,
Alexander Viro, Christian Brauner, David Hildenbrand,
Lorenzo Stoakes, Michal Hocko, Axel Rasmussen, Qi Zheng,
Shakeel Butt, Wei Xu, Yuanchu Xie, Kunwu Chan, linux-mm,
linux-kernel, linux-fsdevel
On Tue, Aug 25, 2026 at 6:54 AM Alexandre Ghiti <alex@ghiti.fr> wrote:
>
> A PG_dropbehind folio is dropped from its cache once writeback completes
> rather than left for reclaim to find later; this is implemented for file
> folios in folio_end_dropbehind(). Extend it to swap cache folios.
>
> The drop takes the folio and swap cluster locks and may sleep, so it
> cannot run in interrupt context. Set BIO_COMPLETE_IN_TASK on the write,
> as the file dropbehind paths do, and drop the folio directly from
> folio_end_writeback().
This BIO_COMPLETE_IN_TASK thing is really nice! I'm glad we have a
proper infra now and no longer have to roll our own solutions :)
>
> Suggested-by: Yosry Ahmed <yosry@kernel.org>
> Suggested-by: Johannes Weiner <hannes@cmpxchg.org>
> Suggested-by: Nhat Pham <nphamcs@gmail.com>
> Signed-off-by: Alexandre Ghiti <alex@ghiti.fr>
Mostly LGTM FWIW. Just one query below:
[...]
> + * Gate remove_mapping_reclaim() on folio_test_swapcache(): a racing
> + * swapin may have freed the swap slot (folio_free_swap()) and dropped the
> + * folio from the cache, and it must not run on a non-swapcache folio (it
> + * would trip __remove_mapping()'s mapping == folio_mapping() check).
> + */
> + if (!folio_test_swapcache(folio) || folio_test_writeback(folio) ||
> + !remove_mapping_reclaim(swap_address_space(folio->swap), folio, memcg)) {
remove_mapping_reclaim() and the likes seem a bit confusing to me, but
I don't have a good suggestions for the naming...
(also I'm hoping we're fixing the weird shadows behavior of zswap
writeback soon :))
Anyway:
Reviewed-by: Nhat Pham <nphamcs@gmail.com>
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v4 3/3] mm: zswap: drop cold writeback folios via swap dropbehind
2026-08-25 13:52 [PATCH v4 0/3] mm: zswap: free cold writeback folios promptly Alexandre Ghiti
2026-08-25 13:52 ` [PATCH v4 1/3] mm: swap: move LRU insertion out of the swap cache allocator Alexandre Ghiti
2026-08-25 13:52 ` [PATCH v4 2/3] mm: swap: drop dropbehind swap cache folios on writeback completion Alexandre Ghiti
@ 2026-08-25 13:52 ` Alexandre Ghiti
2026-08-25 15:51 ` Yosry Ahmed
2026-09-08 8:24 ` Kunwu Chan
2 siblings, 2 replies; 12+ messages in thread
From: Alexandre Ghiti @ 2026-08-25 13:52 UTC (permalink / raw)
To: Johannes Weiner, Yosry Ahmed, Nhat Pham, Andrew Morton, Chris Li,
Kairui Song
Cc: Kairui Song, Chengming Zhou, Matthew Wilcox (Oracle), Jan Kara,
Kemeng Shi, Baoquan He, Barry Song, Youngjun Park, Alexander Viro,
Christian Brauner, David Hildenbrand, Lorenzo Stoakes,
Michal Hocko, Axel Rasmussen, Qi Zheng, Shakeel Butt, Wei Xu,
Yuanchu Xie, Kunwu Chan, linux-mm, linux-kernel, linux-fsdevel,
Alexandre Ghiti
zswap writeback decompresses an entry into a fresh swap cache folio and
writes it back. The folio is cold by construction, yet it is left on the
LRU for reclaim to find and free later, wasting a reclaim scan and keeping
cold memory resident longer than necessary.
Allocate the folio off the LRU and mark it PG_dropbehind so the swap
dropbehind path frees it from the swap cache once writeback completes.
Suggested-by: Johannes Weiner <hannes@cmpxchg.org>
Suggested-by: Nhat Pham <nphamcs@gmail.com>
Signed-off-by: Alexandre Ghiti <alex@ghiti.fr>
---
mm/zswap.c | 19 ++++++++++++++++---
1 file changed, 16 insertions(+), 3 deletions(-)
diff --git a/mm/zswap.c b/mm/zswap.c
index 8163e6c5f76c..d16822a516e8 100644
--- a/mm/zswap.c
+++ b/mm/zswap.c
@@ -1013,7 +1013,6 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
*/
if (IS_ERR(folio))
return PTR_ERR(folio);
- folio_add_lru(folio);
/*
* folio is locked, and the swapcache is now secured against
@@ -1046,12 +1045,26 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
/* folio is up to date */
folio_mark_uptodate(folio);
- /* move it to the tail of the inactive list after end_writeback */
- folio_set_reclaim(folio);
+ folio_set_dropbehind(folio);
+
+ /*
+ * Drop our reference before starting writeback so the swap cache holds
+ * the only one: the drop in folio_end_writeback() needs that for
+ * remove_mapping_reclaim() to succeed, otherwise the folio is handed
+ * back to reclaim instead.
+ *
+ * Nothing can free the folio in the meantime: we hold the folio lock
+ * until writeback starts, PG_writeback then blocks swap cache removal,
+ * and folio_end_writeback() takes its own reference before clearing
+ * PG_writeback and donates it to the drop.
+ */
+ folio_put(folio);
/* start writeback */
__swap_writepage(folio, NULL);
+ return 0;
+
out:
if (ret) {
swap_cache_del_folio(folio);
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH v4 3/3] mm: zswap: drop cold writeback folios via swap dropbehind
2026-08-25 13:52 ` [PATCH v4 3/3] mm: zswap: drop cold writeback folios via swap dropbehind Alexandre Ghiti
@ 2026-08-25 15:51 ` Yosry Ahmed
2026-08-25 16:57 ` Alexandre Ghiti
2026-09-08 8:24 ` Kunwu Chan
1 sibling, 1 reply; 12+ messages in thread
From: Yosry Ahmed @ 2026-08-25 15:51 UTC (permalink / raw)
To: Alexandre Ghiti
Cc: Johannes Weiner, Nhat Pham, Andrew Morton, Chris Li, Kairui Song,
Kairui Song, Chengming Zhou, Matthew Wilcox (Oracle), Jan Kara,
Kemeng Shi, Baoquan He, Barry Song, Youngjun Park, Alexander Viro,
Christian Brauner, David Hildenbrand, Lorenzo Stoakes,
Michal Hocko, Axel Rasmussen, Qi Zheng, Shakeel Butt, Wei Xu,
Yuanchu Xie, Kunwu Chan, linux-mm, linux-kernel, linux-fsdevel
On Tue, Aug 25, 2026 at 6:55 AM Alexandre Ghiti <alex@ghiti.fr> wrote:
>
> zswap writeback decompresses an entry into a fresh swap cache folio and
> writes it back. The folio is cold by construction, yet it is left on the
> LRU for reclaim to find and free later, wasting a reclaim scan and keeping
> cold memory resident longer than necessary.
>
> Allocate the folio off the LRU and mark it PG_dropbehind so the swap
> dropbehind path frees it from the swap cache once writeback completes.
>
> Suggested-by: Johannes Weiner <hannes@cmpxchg.org>
> Suggested-by: Nhat Pham <nphamcs@gmail.com>
> Signed-off-by: Alexandre Ghiti <alex@ghiti.fr>
> ---
> mm/zswap.c | 19 ++++++++++++++++---
> 1 file changed, 16 insertions(+), 3 deletions(-)
>
> diff --git a/mm/zswap.c b/mm/zswap.c
> index 8163e6c5f76c..d16822a516e8 100644
> --- a/mm/zswap.c
> +++ b/mm/zswap.c
> @@ -1013,7 +1013,6 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
> */
> if (IS_ERR(folio))
> return PTR_ERR(folio);
> - folio_add_lru(folio);
>
> /*
> * folio is locked, and the swapcache is now secured against
> @@ -1046,12 +1045,26 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
> /* folio is up to date */
> folio_mark_uptodate(folio);
>
> - /* move it to the tail of the inactive list after end_writeback */
> - folio_set_reclaim(folio);
> + folio_set_dropbehind(folio);
> +
> + /*
> + * Drop our reference before starting writeback so the swap cache holds
> + * the only one: the drop in folio_end_writeback() needs that for
> + * remove_mapping_reclaim() to succeed, otherwise the folio is handed
> + * back to reclaim instead.
> + *
> + * Nothing can free the folio in the meantime: we hold the folio lock
> + * until writeback starts, PG_writeback then blocks swap cache removal,
> + * and folio_end_writeback() takes its own reference before clearing
> + * PG_writeback and donates it to the drop.
> + */
> + folio_put(folio);
>
> /* start writeback */
> __swap_writepage(folio, NULL);
>
> + return 0;
> +
> out:
> if (ret) {
This check is now unneeded as ret should always be non-zero here, right?
We should probably also rename the label to indicate that it's only
the error path. Maybe "error" or "fail"?
> swap_cache_del_folio(folio);
> --
> 2.53.0-Meta
>
>
^ permalink raw reply [flat|nested] 12+ messages in thread* Re: [PATCH v4 3/3] mm: zswap: drop cold writeback folios via swap dropbehind
2026-08-25 15:51 ` Yosry Ahmed
@ 2026-08-25 16:57 ` Alexandre Ghiti
0 siblings, 0 replies; 12+ messages in thread
From: Alexandre Ghiti @ 2026-08-25 16:57 UTC (permalink / raw)
To: Yosry Ahmed
Cc: Alexandre Ghiti, Johannes Weiner, Nhat Pham, Andrew Morton,
Chris Li, Kairui Song, Kairui Song, Chengming Zhou,
Matthew Wilcox (Oracle), Jan Kara, Kemeng Shi, Baoquan He,
Barry Song, Youngjun Park, Alexander Viro, Christian Brauner,
David Hildenbrand, Lorenzo Stoakes, Michal Hocko, Axel Rasmussen,
Qi Zheng, Shakeel Butt, Wei Xu, Yuanchu Xie, Kunwu Chan, linux-mm,
linux-kernel, linux-fsdevel
Hi Yosry,
On Tue, Aug 25, 2026 at 5:52 PM Yosry Ahmed <yosry@kernel.org> wrote:
>
> >
> On Tue, Aug 25, 2026 at 6:55 AM Alexandre Ghiti <alex@ghiti.fr> wrote:
> >
> > zswap writeback decompresses an entry into a fresh swap cache folio and
> > writes it back. The folio is cold by construction, yet it is left on the
> > LRU for reclaim to find and free later, wasting a reclaim scan and keeping
> > cold memory resident longer than necessary.
> >
> > Allocate the folio off the LRU and mark it PG_dropbehind so the swap
> > dropbehind path frees it from the swap cache once writeback completes.
> >
> > Suggested-by: Johannes Weiner <hannes@cmpxchg.org>
> > Suggested-by: Nhat Pham <nphamcs@gmail.com>
> > Signed-off-by: Alexandre Ghiti <alex@ghiti.fr>
> > ---
> > mm/zswap.c | 19 ++++++++++++++++---
> > 1 file changed, 16 insertions(+), 3 deletions(-)
> >
> > diff --git a/mm/zswap.c b/mm/zswap.c
> > index 8163e6c5f76c..d16822a516e8 100644
> > --- a/mm/zswap.c
> > +++ b/mm/zswap.c
> > @@ -1013,7 +1013,6 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
> > */
> > if (IS_ERR(folio))
> > return PTR_ERR(folio);
> > - folio_add_lru(folio);
> >
> > /*
> > * folio is locked, and the swapcache is now secured against
> > @@ -1046,12 +1045,26 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
> > /* folio is up to date */
> > folio_mark_uptodate(folio);
> >
> > - /* move it to the tail of the inactive list after end_writeback */
> > - folio_set_reclaim(folio);
> > + folio_set_dropbehind(folio);
> > +
> > + /*
> > + * Drop our reference before starting writeback so the swap cache holds
> > + * the only one: the drop in folio_end_writeback() needs that for
> > + * remove_mapping_reclaim() to succeed, otherwise the folio is handed
> > + * back to reclaim instead.
> > + *
> > + * Nothing can free the folio in the meantime: we hold the folio lock
> > + * until writeback starts, PG_writeback then blocks swap cache removal,
> > + * and folio_end_writeback() takes its own reference before clearing
> > + * PG_writeback and donates it to the drop.
> > + */
> > + folio_put(folio);
> >
> > /* start writeback */
> > __swap_writepage(folio, NULL);
> >
> > + return 0;
> > +
> > out:
> > if (ret) {
>
> This check is now unneeded as ret should always be non-zero here, right?
Yes, I missed it!
>
> We should probably also rename the label to indicate that it's only
> the error path. Maybe "error" or "fail"?
Sure, will do in v5.
Thanks,
Alex
>
> > swap_cache_del_folio(folio);
> > --
> > 2.53.0-Meta
> >
> >
>
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v4 3/3] mm: zswap: drop cold writeback folios via swap dropbehind
2026-08-25 13:52 ` [PATCH v4 3/3] mm: zswap: drop cold writeback folios via swap dropbehind Alexandre Ghiti
2026-08-25 15:51 ` Yosry Ahmed
@ 2026-09-08 8:24 ` Kunwu Chan
1 sibling, 0 replies; 12+ messages in thread
From: Kunwu Chan @ 2026-09-08 8:24 UTC (permalink / raw)
To: Alexandre Ghiti
Cc: Kunwu Chan, Johannes Weiner, Yosry Ahmed, Nhat Pham,
Andrew Morton, Chris Li, Kairui Song, Kairui Song, Chengming Zhou,
Matthew Wilcox (Oracle), Jan Kara, Kemeng Shi, Baoquan He,
Barry Song, Youngjun Park, Alexander Viro, Christian Brauner,
David Hildenbrand, Lorenzo Stoakes, Michal Hocko, Axel Rasmussen,
Qi Zheng, Shakeel Butt, Wei Xu, Yuanchu Xie, linux-mm,
linux-kernel, linux-fsdevel
On Tue, 25 Aug 2026 15:52:07 +0200 Alexandre Ghiti <alex@ghiti.fr> wrote:
> zswap writeback decompresses an entry into a fresh swap cache folio and
> writes it back. The folio is cold by construction, yet it is left on the
> LRU for reclaim to find and free later, wasting a reclaim scan and keeping
> cold memory resident longer than necessary.
>
> Allocate the folio off the LRU and mark it PG_dropbehind so the swap
> dropbehind path frees it from the swap cache once writeback completes.
>
> Suggested-by: Johannes Weiner <hannes@cmpxchg.org>
> Suggested-by: Nhat Pham <nphamcs@gmail.com>
> Signed-off-by: Alexandre Ghiti <alex@ghiti.fr>
> ---
> mm/zswap.c | 19 ++++++++++++++++---
> 1 file changed, 16 insertions(+), 3 deletions(-)
>
> diff --git a/mm/zswap.c b/mm/zswap.c
> index 8163e6c5f76c..d16822a516e8 100644
> --- a/mm/zswap.c
> +++ b/mm/zswap.c
> @@ -1013,7 +1013,6 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
> */
> if (IS_ERR(folio))
> return PTR_ERR(folio);
> - folio_add_lru(folio);
>
> /*
> * folio is locked, and the swapcache is now secured against
> @@ -1046,12 +1045,26 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
> /* folio is up to date */
> folio_mark_uptodate(folio);
>
> - /* move it to the tail of the inactive list after end_writeback */
> - folio_set_reclaim(folio);
> + folio_set_dropbehind(folio);
> +
> + /*
> + * Drop our reference before starting writeback so the swap cache holds
> + * the only one: the drop in folio_end_writeback() needs that for
> + * remove_mapping_reclaim() to succeed, otherwise the folio is handed
> + * back to reclaim instead.
> + *
> + * Nothing can free the folio in the meantime: we hold the folio lock
> + * until writeback starts, PG_writeback then blocks swap cache removal,
> + * and folio_end_writeback() takes its own reference before clearing
> + * PG_writeback and donates it to the drop.
> + */
> + folio_put(folio);
>
> /* start writeback */
> __swap_writepage(folio, NULL);
Thanks for addressing this.
I checked the refcount handoff in v4. Dropping the zswap reference before
__swap_writepage() removes the refcount overlap I was concerned about in v3.
The swapcache reference keeps the folio alive until writeback starts, and
folio_end_writeback() takes the completion reference before clearing
PG_writeback, so remove_mapping_reclaim() sees the expected refcount.
Reviewed-by: Kunwu Chan <kunwu.chan@gmail.com>
Thanks,
KunWu
>
> + return 0;
> +
> out:
> if (ret) {
> swap_cache_del_folio(folio);
> --
> 2.53.0-Meta
>
>
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v4 0/3] mm: fix workingset refaults in the zswap writeback path
@ 2026-09-11 9:20 Alexandre Ghiti
2026-09-11 9:20 ` [PATCH v4 1/3] mm: swap: move LRU insertion out of the swap cache allocator Alexandre Ghiti
0 siblings, 1 reply; 12+ messages in thread
From: Alexandre Ghiti @ 2026-09-11 9:20 UTC (permalink / raw)
To: Johannes Weiner, Yosry Ahmed, Nhat Pham, Chengming Zhou,
Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Hugh Dickins, Baolin Wang,
Chris Li, Kairui Song, Kemeng Shi, Baoquan He, Barry Song,
Youngjun Park, Qi Zheng, Shakeel Butt, Axel Rasmussen,
Yuanchu Xie, Wei Xu, Joonsoo Kim
Cc: linux-mm, linux-kernel, Alexandre Ghiti
Note: patch 1 also appears as patch 1 of the zswap dropbehind series [1].
It is the same change. Both series need it and both are meant to apply on
their own, so it is posted in each; whichever lands first, the other
should drop it.
When an anonymous folio is reclaimed, workingset_eviction() stores a
"shadow" (the eviction cookie) in the swap slot so that a later swap-in
can be recognised as a refault and, if the refault distance is short
enough, the page can be re-activated. This is how anon workingset/refault
detection has worked since commit aae466b0052e ("mm/swap: implement
workingset detection for anonymous LRU").
zswap writeback breaks this in two independent ways:
- Over-count at writeback: the shrinker allocates a buffer folio in the
swap cache, and the allocation path counts that folio as a refault.
- Lost eviction cookie at reclaim: adding the buffer to the swap cache
overwrites the slot's shadow, so the original cookie is lost; when the
buffer folio is finally reclaimed a fresh, inaccurate cookie is minted
in its place.
This series preserves the shadow within zswap itself, without any new
swap-table or swap-slot state. At writeback, instead of erasing the freed
zswap entry, the captured shadow is parked in the zswap tree in its place,
so it outlives the writeback buffer folio.
Finally, the refault evaluation is moved out of the swap-cache allocator
into the swap-in callers, so allocating the writeback buffer is no longer
miscounted as a refault.
Results
-------
Measured with a sysbench OLTP (MariaDB) workload in a memory cgroup sized
so the dataset and InnoDB buffer pool both overcommit it, with the zswap
shrinker on so entries are continuously written back to an NVMe swap
device (classic LRU; MGLRU off). Anon workingset counters over the
measured window, baseline vs this series, mean +/- stddev over 10 runs:
workingset_refault_anon 383,242 +/- 59,345 -> 199,355 +/- 27,042 -48%
workingset_activate_anon 54,890 +/- 11,742 -> 22,607 +/- 3,118 -59%
workingset_restore_anon 16,212 +/- 4,590 -> 7,599 +/- 1,190 -53%
Writeback volume is comparable (zswpwb 183k +/- 16k -> 178k +/- 15k), so
the reduction is not from doing less work. Normalised per transaction the
reduction holds (-53%/-49%/-39%) while the swap work per transaction is
unchanged. A kernel build under the same pressure moves all three
counters in the same direction.
The run-to-run variance of these counters drops as well.
Throughput is unaffected: over the same 10 runs, transactions/s is
18.50 +/- 1.08 -> 18.65 +/- 0.94, i.e. +0.8% with a 95% confidence
interval of +/- 7.7%.
Changes in v4:
- Usama pointed out that patch 1's changelog referred to "the next patch"
and to the "upcoming" dropbehind series, which does not work for a patch
posted in two series. Both references are gone.
- Kunwu Chan pointed out that mm/swapfile.c still refers to the function
by its old name in a comment; patch 1 renames it there too.
- Usama pointed out that reading the slot's shadow in the swap-in path
before allocating races: the allocation can sleep, so another swap-in
can install a folio, have it reclaimed and leave a newer shadow behind,
and this caller would then win the insertion but refault against the
stale snapshot. __swap_cache_alloc_folio() now hands the shadow back
from __swap_cache_add_check(), which captures it under ci->lock at the
point the displacing insertion succeeds. zswap writeback took the same
racy snapshot and now uses the same output.
Changes in v3:
- David pointed out that the boolean added to workingset_refault() in v2
makes the calling code hard to read. The boolean is now internal to
mm/workingset.c and the callers use workingset_refault() or
workingset_refault_lru_managed(), the same way remove_mapping() and
remove_mapping_reclaim() do. The three existing callers are left
untouched.
- zswap_writeback_entry() now restores the shadow into the slot on the
error paths taken after the buffer was allocated: the allocation had
already overwritten it and nothing was parked yet, so it was lost.
- Dropped the "if (!shadow) shadow = ZSWAP_WRITEBACK_NO_SHADOW" fallback,
which can never be taken: the swap table already marks a swapped out
slot with xa_mk_value(0), so swap_cache_get_shadow() never returns NULL
for one.
- Rebased on mm-new.
Changes in v2:
- Sashiko pointed out that v1 evaluated the refault after folio_add_lru(),
which picks the MGLRU generation before PG_workingset is set. Patch 1
now moves the LRU insertion out of the swap cache allocator so the
refault is evaluated before it, as it was originally.
- Sashiko also pointed out that a failed writeback redirties the buffer
and leaves it in the swap cache with its shadow still parked, so a
later zswap_store() on it would hand the parked value to
zswap_entry_free(). zswap_store() now bails out for such a folio:
writeback already decided that data belongs on disk, so it is written
there instead of being compressed again, which also keeps the parked
shadow intact until the folio leaves the swap cache.
- The buffer folio a swap-in consumes is already on the LRU, so the
refault there cannot activate it by setting PG_active: that leaves the
flag disagreeing with the list the folio is on, which shows up as an
mm/memcontrol.c lru_size underflow when it is freed. Such a folio is now
activated with folio_activate() instead. One still sitting in a per-CPU
batch cannot be moved safely and just misses the activation; a counter
on that path measured 0.0007% of the activations over a 10 run test.
This is only needed because zswap writeback still puts its buffer on the
LRU: once it stops doing so, workingset_refault_lru_managed() has no
caller left and can go away.
[1] https://lore.kernel.org/linux-mm/20260825135209.3135169-2-alex@ghiti.fr/
v1: https://lore.kernel.org/all/20260817144622.137133-1-alex@ghiti.fr/
v2: https://lore.kernel.org/linux-mm/20260821093606.2231216-1-alex@ghiti.fr/
v3: https://lore.kernel.org/linux-mm/20260825172604.3243589-1-alex@ghiti.fr/
Alexandre Ghiti (3):
mm: swap: move LRU insertion out of the swap cache allocator
mm: swap: refault on swap-in, not in the swap cache allocator
mm: zswap: preserve the workingset shadow across writeback
include/linux/zswap.h | 12 +++++
mm/internal.h | 1 +
mm/memory.c | 5 ++
mm/shmem.c | 5 ++
mm/swap.h | 7 +--
mm/swap_state.c | 52 +++++++++++++++-----
mm/swapfile.c | 2 +-
mm/vmscan.c | 4 +-
mm/workingset.c | 60 ++++++++++++++++++-----
mm/zswap.c | 108 ++++++++++++++++++++++++++++++++++++++++--
10 files changed, 224 insertions(+), 32 deletions(-)
base-commit: 1a46b1e97bde62afa7d925bb0dcd9f9748a1d7c3
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH v4 1/3] mm: swap: move LRU insertion out of the swap cache allocator
2026-09-11 9:20 [PATCH v4 0/3] mm: fix workingset refaults in the zswap writeback path Alexandre Ghiti
@ 2026-09-11 9:20 ` Alexandre Ghiti
0 siblings, 0 replies; 12+ messages in thread
From: Alexandre Ghiti @ 2026-09-11 9:20 UTC (permalink / raw)
To: Johannes Weiner, Yosry Ahmed, Nhat Pham, Chengming Zhou,
Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Hugh Dickins, Baolin Wang,
Chris Li, Kairui Song, Kemeng Shi, Baoquan He, Barry Song,
Youngjun Park, Qi Zheng, Shakeel Butt, Axel Rasmussen,
Yuanchu Xie, Wei Xu, Joonsoo Kim
Cc: linux-mm, linux-kernel, Alexandre Ghiti, Kunwu Chan, Usama Arif
This is a preparatory patch.
__swap_cache_alloc_folio() adds the new folio to the LRU itself, which
leaves its callers no way to act on the folio before it becomes visible
to reclaim. Two users need exactly that:
- moving the refault evaluation out of the swap cache folio allocation
requires it to happen before folio_add_lru(): that consumes PG_active
to file the folio on the inactive or the active list, and under MGLRU
it also reads PG_workingset to pick the generation. Setting either
flag afterwards does not move the folio;
- zswap writeback dropbehind needs the buffer folio to stay off the LRU
entirely, as the per-CPU LRU batch would hold a reference on it and
keep remove_mapping() from freeing it once writeback completes.
Defer the LRU insertion to the callers of __swap_cache_alloc_folio():
each of them adds the folio right after the allocation, so there is no
functional change intended.
Suggested-by: Kairui Song <kasong@tencent.com>
Reviewed-by: Nhat Pham <nphamcs@gmail.com>
Reviewed-by: Kunwu Chan <kunwu.chan@gmail.com>
Acked-by: Usama Arif <usama.arif@linux.dev>
Signed-off-by: Alexandre Ghiti <alex@ghiti.fr>
---
mm/swap.h | 6 +++---
mm/swap_state.c | 20 ++++++++++++--------
mm/swapfile.c | 2 +-
mm/zswap.c | 5 +++--
4 files changed, 19 insertions(+), 14 deletions(-)
diff --git a/mm/swap.h b/mm/swap.h
index 90a551a88df6..8679cb61268e 100644
--- a/mm/swap.h
+++ b/mm/swap.h
@@ -312,9 +312,9 @@ bool swap_cache_has_folio(swp_entry_t entry);
struct folio *swap_cache_get_folio(swp_entry_t entry);
void *swap_cache_get_shadow(swp_entry_t entry);
void swap_cache_del_folio(struct folio *folio);
-struct folio *swap_cache_alloc_folio(swp_entry_t target_entry, gfp_t gfp_mask,
- unsigned long orders, struct vm_fault *vmf,
- struct mempolicy *mpol, pgoff_t ilx);
+struct folio *__swap_cache_alloc_folio(swp_entry_t target_entry, gfp_t gfp_mask,
+ unsigned long orders, struct vm_fault *vmf,
+ struct mempolicy *mpol, pgoff_t ilx);
/* Below helpers require the caller to lock and pass in the swap cluster. */
void __swap_cache_add_folio(struct swap_cluster_info *ci,
struct folio *folio, swp_entry_t entry);
diff --git a/mm/swap_state.c b/mm/swap_state.c
index b76eb3d876fd..bf8ff2d2dbf1 100644
--- a/mm/swap_state.c
+++ b/mm/swap_state.c
@@ -489,13 +489,11 @@ static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,
node_stat_mod_folio(folio, NR_FILE_PAGES, nr_pages);
lruvec_stat_mod_folio(folio, NR_SWAPCACHE, nr_pages);
- /* Caller will initiate read into locked new_folio */
- folio_add_lru(folio);
return folio;
}
/**
- * swap_cache_alloc_folio - Allocate folio for swapped out slot in swap cache.
+ * __swap_cache_alloc_folio - Allocate folio for swapped out slot in swap cache.
* @targ_entry: swap entry indicating the target slot
* @gfp: memory allocation flags
* @orders: allocation orders, must be non zero
@@ -507,13 +505,17 @@ static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,
* doing IO (e.g. swap in or zswap writeback). The swap slot indicated by
* @targ_entry must have a non-zero swap count (swapped out).
*
+ * The returned folio is locked and is NOT on the LRU. The caller must either
+ * add it to the LRU with folio_add_lru() so page reclaim can find it, or free
+ * it directly once done; a folio left off the LRU is unreclaimable and leaks.
+ *
* Context: Caller must protect the swap device with reference count or locks.
* Return: Returns the folio if allocation succeeded and folio is in the swap
* cache. Returns error code if failed due to race, OOM or invalid arguments.
*/
-struct folio *swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,
- unsigned long orders, struct vm_fault *vmf,
- struct mempolicy *mpol, pgoff_t ilx)
+struct folio *__swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,
+ unsigned long orders, struct vm_fault *vmf,
+ struct mempolicy *mpol, pgoff_t ilx)
{
int order, err;
struct folio *ret;
@@ -649,12 +651,13 @@ static struct folio *swap_cache_read_folio(struct swap_io_ctx *ctx,
folio = swap_cache_get_folio(entry);
if (folio)
return folio;
- folio = swap_cache_alloc_folio(entry, gfp, BIT(0), NULL, mpol, ilx);
+ 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;
+ folio_add_lru(folio);
swap_read_folio(ctx, folio);
if (readahead) {
folio_set_readahead(folio);
@@ -690,12 +693,13 @@ struct folio *swapin_sync(swp_entry_t entry, gfp_t gfp, unsigned long orders,
folio = swap_cache_get_folio(entry);
if (folio)
return folio;
- folio = swap_cache_alloc_folio(entry, gfp, orders, vmf, mpol, ilx);
+ folio = __swap_cache_alloc_folio(entry, gfp, orders, vmf, mpol, ilx);
} while (PTR_ERR(folio) == -EEXIST);
if (IS_ERR(folio))
return folio;
+ folio_add_lru(folio);
swap_read_folio(&ctx, folio);
swap_read_submit(&ctx);
return folio;
diff --git a/mm/swapfile.c b/mm/swapfile.c
index 51f5a525bb61..bfd0fb46b0ad 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -1870,7 +1870,7 @@ void folio_put_swap(struct folio *folio, struct page *page)
* CPU1 CPU2
* do_swap_page()
* ... swapoff+swapon
- * swap_cache_alloc_folio()
+ * __swap_cache_alloc_folio()
* // check swap_map
* // verify PTE not changed
*
diff --git a/mm/zswap.c b/mm/zswap.c
index 37f34e406c8e..0d2efe21f18a 100644
--- a/mm/zswap.c
+++ b/mm/zswap.c
@@ -1001,8 +1001,8 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
return -EEXIST;
mpol = get_task_policy(current);
- folio = swap_cache_alloc_folio(swpentry, GFP_KERNEL, BIT(0), NULL, mpol,
- NO_INTERLEAVE_INDEX);
+ folio = __swap_cache_alloc_folio(swpentry, GFP_KERNEL, BIT(0), NULL, mpol,
+ NO_INTERLEAVE_INDEX);
put_swap_device(si);
/*
@@ -1014,6 +1014,7 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
*/
if (IS_ERR(folio))
return PTR_ERR(folio);
+ folio_add_lru(folio);
/*
* folio is locked, and the swapcache is now secured against
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-09-11 9:21 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-25 13:52 [PATCH v4 0/3] mm: zswap: free cold writeback folios promptly Alexandre Ghiti
2026-08-25 13:52 ` [PATCH v4 1/3] mm: swap: move LRU insertion out of the swap cache allocator Alexandre Ghiti
2026-09-08 9:37 ` Kunwu Chan
2026-09-08 18:26 ` Nhat Pham
2026-08-25 13:52 ` [PATCH v4 2/3] mm: swap: drop dropbehind swap cache folios on writeback completion Alexandre Ghiti
2026-09-08 9:58 ` Kunwu Chan
2026-09-08 18:39 ` Nhat Pham
2026-08-25 13:52 ` [PATCH v4 3/3] mm: zswap: drop cold writeback folios via swap dropbehind Alexandre Ghiti
2026-08-25 15:51 ` Yosry Ahmed
2026-08-25 16:57 ` Alexandre Ghiti
2026-09-08 8:24 ` Kunwu Chan
-- strict thread matches above, loose matches on Subject: below --
2026-09-11 9:20 [PATCH v4 0/3] mm: fix workingset refaults in the zswap writeback path Alexandre Ghiti
2026-09-11 9:20 ` [PATCH v4 1/3] mm: swap: move LRU insertion out of the swap cache allocator Alexandre Ghiti
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox