Re: [PATCH 2/2] mm: zswap: deferred dropbehind free of writeback folios

From: Nhat Pham

Date: Mon Jul 20 2026 - 11:57:44 EST


On Sat, Jul 18, 2026 at 2:39 AM Alexandre Ghiti <alex@xxxxxxxx> wrote:
>
> The previous change frees zswap writeback folios promptly for
> synchronous-IO swap, where writeback completes in the calling context.
> Asynchronous block devices and filesystem-backed swap complete
> writeback in interrupt context, where the folio cannot be freed, so
> those cold folios are still left on the LRU for reclaim to clean up
> later.
>
> Free them promptly as well by deferring the work to a workqueue that
> runs once writeback has completed.
>
> Suggested-by: Johannes Weiner <hannes@xxxxxxxxxxx>
> Signed-off-by: Alexandre Ghiti <alex@xxxxxxxx>

No Suggested-by for poor Nhat? :(

Just kidding, of course - thanks for working on this cool idea :)

> ---
> include/linux/zswap.h | 2 ++
> mm/filemap.c | 20 ++++++++++++++++++++
> mm/zswap.c | 34 +++++++++++++++++++++++++++++++---
> 3 files changed, 53 insertions(+), 3 deletions(-)
>
> diff --git a/include/linux/zswap.h b/include/linux/zswap.h
> index 30c193a1207e..23dcde274964 100644
> --- a/include/linux/zswap.h
> +++ b/include/linux/zswap.h
> @@ -35,6 +35,7 @@ void zswap_lruvec_state_init(struct lruvec *lruvec);
> void zswap_folio_swapin(struct folio *folio);
> bool zswap_is_enabled(void);
> bool zswap_never_enabled(void);
> +void zswap_writeback_dropbehind_folio(struct folio *folio);
> #else
>
> struct zswap_lruvec_state {};
> @@ -58,6 +59,7 @@ static inline void zswap_swapoff(int type) {}
> static inline void zswap_memcg_offline_cleanup(struct mem_cgroup *memcg) {}
> static inline void zswap_lruvec_state_init(struct lruvec *lruvec) {}
> static inline void zswap_folio_swapin(struct folio *folio) {}
> +static inline void zswap_writeback_dropbehind_folio(struct folio *folio) {}
>
> static inline bool zswap_is_enabled(void)
> {
> diff --git a/mm/filemap.c b/mm/filemap.c
> index dc3a0e960b9f..548e465b5b9a 100644
> --- a/mm/filemap.c
> +++ b/mm/filemap.c
> @@ -21,6 +21,7 @@
> #include <linux/gfp.h>
> #include <linux/mm.h>
> #include <linux/swap.h>
> +#include <linux/zswap.h>
> #include <linux/leafops.h>
> #include <linux/syscalls.h>
> #include <linux/mman.h>
> @@ -1680,6 +1681,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);
>
> /*
> @@ -1689,7 +1692,24 @@ void folio_end_writeback(struct folio *folio)
> * reused before the folio_wake_bit().
> */
> folio_get(folio);
> +
> + /*
> + * zswap writeback folios are off-LRU, so we must prevent a racing
> + * swapin from removing the folio from the swap cache and keeping it
> + * off-LRU: the writeback flag allows that. Afterwards a swapin may win
> + * the race, but the folio is already queued and the worker puts it back
> + * on the LRU in that case.
> + */
> + swap_dropbehind = folio_test_swapcache(folio) &&
> + folio_test_dropbehind(folio);
> +
> folio_end_writeback_no_dropbehind(folio);
> +
> + if (swap_dropbehind) {
> + zswap_writeback_dropbehind_folio(folio);
> + return;
> + }
> +
> folio_end_dropbehind(folio);
> folio_put(folio);
> }
> diff --git a/mm/zswap.c b/mm/zswap.c
> index 3c494c5a671c..d185da85bb68 100644
> --- a/mm/zswap.c
> +++ b/mm/zswap.c
> @@ -34,6 +34,7 @@
> #include <linux/writeback.h>
> #include <linux/pagemap.h>
> #include <linux/workqueue.h>
> +#include <linux/llist.h>
> #include <linux/list_lru.h>
> #include <linux/zsmalloc.h>
>
> @@ -990,12 +991,39 @@ static void zswap_writeback_free_folio(struct folio *folio)
> goto out;
>
> /* Raced: the folio is now owned by the swapin; put it back on the LRU. */
> + folio_clear_dropbehind(folio);
> folio_add_lru(folio);
> out:
> folio_unlock(folio);
> folio_put(folio);
> }
>
> +static DEFINE_PER_CPU(struct llist_head, zswap_dropbehind_llist);
> +
> +static void zswap_dropbehind_workfn(struct work_struct *work)
> +{
> + struct llist_node *pos, *next;
> + int cpu;
> +
> + for_each_possible_cpu(cpu) {
> + pos = llist_del_all(per_cpu_ptr(&zswap_dropbehind_llist, cpu));
> + llist_for_each_safe(pos, next, pos) {
> + struct folio *folio = container_of((struct list_head *)pos,
> + struct folio, lru);
> + zswap_writeback_free_folio(folio);

nit: I wonder if it's worth doing some batching here for the free step.

i.e, after remove_mapping() succeeds for a folio, we can just add the
folio to a batch, then call folios_put_refs() in the entire batch :-?