Re: [PATCH 2/2] mm: zswap: deferred dropbehind free of writeback folios
From: Alexandre Ghiti
Date: Tue Jul 21 2026 - 09:57:12 EST
Hi Nhat,
On Mon, Jul 20, 2026 at 5:57 PM Nhat Pham <nphamcs@xxxxxxxxx> wrote:
>
> >
> 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 :)
Oh man I'm sorry, here is the story: while working on the large folio
zswapin, I noticed the swapcache was getting huge when the shrinker
was enabled. I mentioned this to Johannes, who suggested dropbehind
(this was the first time I heard about it). And then you told me you
had already tried it yourself!
>
> > ---
> > 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 :-?
Did not think of that, I'll do!
Thanks,
Alex