Re: [RFC PATCH] mm: Avoiding split large folios if swap has no space

From: Kairui Song

Date: Sat Jun 20 2026 - 03:20:17 EST


On Sat, Jun 20, 2026 at 6:42 AM Barry Song (Xiaomi) <baohua@xxxxxxxxxx> wrote:
>
> On Sat, Jun 20, 2026 at 3:18 AM Kairui Song <ryncsn@xxxxxxxxx> wrote:
> >
> >
> > Hello Barry,
> >
> > Thanks for raising this issue. I saw a similar issue report in the
> > mail list before and was thinking that, perhaps another approach is to
>
> Hi Kairui,
>
> Could you please post the link to your report? I'd like to add
> your Reported-by and Closes tags as well.

Hello, here is my previous reply:
https://lore.kernel.org/linux-mm/CAMgjq7AD2ivdgD_kRx+0+w0M=Fvz5dXepw84_Ui_q7=97B4Ofw@xxxxxxxxxxxxxx/

It's the same issue, not reported by me though.
>
> > let folio_alloc_swap return a more detailed error code, for example:
> >
> > - 1. the mem_cgroup_try_charge_swap in it failed
> > - 2. allocation failed but nr_swap_pages > folio size
> > - 3. allocation failed because all devices are full or unusable
> > (roughly nr_swap_pages < folio size)
> >
>
> folio_alloc_swap() returns error codes such as -EAGAIN,
> -EINVAL, and -ENOMEM. For cases 1, 2, and 3, I assume it
> would return -ENOMEM?
>
> I assume you mean that we might want folio_alloc_swap() to
> return an enum instead?
>
> another approach is that I can return -EAGAIN for those cases
> we want to retry swapping-out after splitting folios:

Right, I don't have a strong preference on this.

> diff --git a/mm/swapfile.c b/mm/swapfile.c
> index 78b49b0658ad..62e2c506ccae 100644
> --- a/mm/swapfile.c
> +++ b/mm/swapfile.c
> @@ -1755,6 +1755,9 @@ int folio_alloc_swap(struct folio *folio)
> VM_WARN_ON_ONCE(1);
> return -EINVAL;
> }
> +
> + if (get_nr_swap_pages() < (1 << order))
> + return -ENOMEM;
> }
>
> again:
> @@ -1769,11 +1772,13 @@ int folio_alloc_swap(struct folio *folio)
> }
>
> /* Need to call this even if allocation failed, for MEMCG_SWAP_FAIL. */
> - if (unlikely(mem_cgroup_try_charge_swap(folio)))
> + if (unlikely(mem_cgroup_try_charge_swap(folio))) {
> swap_cache_del_folio(folio);
> + return -ENOMEM;
> + }
>
> if (unlikely(!folio_test_swapcache(folio)))
> - return -ENOMEM;
> + return -EAGAIN;
>
> return 0;
> }
> diff --git a/mm/vmscan.c b/mm/vmscan.c
> index 299b5d9e8836..4c4cbd72c013 100644
> --- a/mm/vmscan.c
> +++ b/mm/vmscan.c
> @@ -1257,6 +1257,8 @@ static unsigned int shrink_folio_list(struct list_head *folio_list,
> */
> if (folio_test_anon(folio) && folio_test_swapbacked(folio) &&
> !folio_test_swapcache(folio)) {
> + int ret;
> +
> if (!(sc->gfp_mask & __GFP_IO))
> goto keep_locked;
> if (folio_maybe_dma_pinned(folio))
> @@ -1275,25 +1277,24 @@ static unsigned int shrink_folio_list(struct list_head *folio_list,
> split_folio_to_list(folio, folio_list))
> goto activate_locked;
> }
> - if (folio_alloc_swap(folio)) {
> - int __maybe_unused order = folio_order(folio);
> + ret = folio_alloc_swap(folio);
> + if (!folio_test_large(folio) || ret != -EAGAIN)
> + goto activate_locked_split;
>
> - if (!folio_test_large(folio))
> - goto activate_locked_split;
> - /* Fallback to swap normal pages */
> - if (split_folio_to_list(folio, folio_list))
> - goto activate_locked;
> + /* Fallback to swap normal pages */
> + if (split_folio_to_list(folio, folio_list))
> + goto activate_locked;
> #ifdef CONFIG_TRANSPARENT_HUGEPAGE
> - if (nr_pages >= HPAGE_PMD_NR) {
> - count_memcg_folio_events(folio,
> + if (nr_pages >= HPAGE_PMD_NR) {
> + count_memcg_folio_events(folio,
> THP_SWPOUT_FALLBACK, 1);
> - count_vm_event(THP_SWPOUT_FALLBACK);
> - }
> -#endif
> - count_mthp_stat(order, MTHP_STAT_SWPOUT_FALLBACK);
> - if (folio_alloc_swap(folio))
> - goto activate_locked_split;
> + count_vm_event(THP_SWPOUT_FALLBACK);
> }
> +#endif
> + count_mthp_stat(folio_order(folio), MTHP_STAT_SWPOUT_FALLBACK);
> + if (folio_alloc_swap(folio))
> + goto activate_locked_split;
> +
> /*
> * Normally the folio will be dirtied in unmap because
> * its pte should be dirty. A special case is MADV_FREE
>
> > Only case 2 requires splitting. __can_reclaim_anon_pages also checks
> > demote which is not related to swap.
>
> I actually extracted __can_reclaim_anon_pages(), which only
> checks swap, whereas can_reclaim_anon_pages() checks both
> swap and demotion. :-)

Ah yeah you are right, I didn't see that part.