* [RFC PATCH v3 0/4] mm: avoid large folio splits when swap is unavailable
@ 2026-07-17 12:25 Xueyuan Chen
2026-07-17 12:25 ` [RFC PATCH v3 1/4] mm: add page_counter_margin() Xueyuan Chen
` (3 more replies)
0 siblings, 4 replies; 12+ messages in thread
From: Xueyuan Chen @ 2026-07-17 12:25 UTC (permalink / raw)
To: akpm, linux-mm
Cc: linux-kernel, cgroups, baohua, zhaonanzhe, hannes, mhocko,
roman.gushchin, shakeel.butt, muchun.song, chrisl, kasong,
shikemeng, nphamcs, bhe, youngjun.park, david, ljs, liam, vbabka,
rppt, surenb, qi.zheng, axelrasmussen, yuanchu, weixugc,
baolin.wang, hughd, Xueyuan Chen
This is an RFC v3 of Barry's original RFC patch, "mm: Avoiding split
large folios if swap has no space":
https://lore.kernel.org/r/20260618221720.71768-1-baohua@kernel.org
Barry's RFC showed the no-swap case with MADV_PAGEOUT on 16KB mTHP: the
large-folio split counter increased by 1024 even though no swapout
progress was possible. Skipping the split in that case kept the counter
at 0.
This version keeps that behavior, but makes folio_alloc_swap() classify
the failure. The helper has both the swap allocation result and the memcg
swap charge result, so callers only need to split when folio_alloc_swap()
reports that a smaller folio might still be swapped out.
Patch #1 adds page_counter_margin(), a small helper that computes the
minimum remaining chargeable space across a page_counter hierarchy.
Patch #2 uses that helper in the memcg swap path and lets
folio_alloc_swap() distinguish large-folio swap allocation failures:
- -E2BIG: splitting may let smaller folios make progress
- -ENOSPC: no global swap space is available
- -ENOMEM: splitting is not expected to help, including memcg swap
charge failures with no remaining swap capacity
Patch #3 makes vmscan split a large folio only when folio_alloc_swap()
returns -E2BIG. Other failures keep the existing activation path and avoid
destroying the large folio when no smaller part can be backed by swap
either.
Patch #4 applies the same contract to shmem_writeout(), which previously
split a large folio on every folio_alloc_swap() failure. It now enters the
split fallback only on -E2BIG; other failures redirty and reactivate the
folio as before.
RFC v2 -> RFC v3:
- Use Johannes Weiner's original page_counter_margin() patch and preserve
his authorship. Move the mem_cgroup_get_nr_swap_pages() conversion into
Patch #1 so the helper addition remains a pure refactoring.
- Add Patch #4 to make shmem_writeout() split large folios only on -E2BIG,
as suggested by Baolin Wang.
- Previous version:
https://lore.kernel.org/r/20260709145124.764807-1-xueyuan.chen21@gmail.com
RFC v1 -> RFC v2:
- Split the RFC into helper, swap allocation, and vmscan patches.
- Add page_counter_margin() and use it for hierarchical memcg swap
capacity checks.
- Make folio_alloc_swap() return -E2BIG only when a smaller folio may
still be swapped out.
- Return -ENOSPC for no global swap space and -ENOMEM when splitting is
not expected to help, including memcg swap exhaustion.
- Make vmscan split large folios only on -E2BIG from folio_alloc_swap().
Barry Song (Xiaomi) (1):
mm/vmscan: avoid pointless large folio splits without swap
Johannes Weiner (1):
mm: add page_counter_margin()
Xueyuan Chen (2):
mm: distinguish large folio swap allocation failures
mm/shmem: split large folios only on -E2BIG
include/linux/page_counter.h | 1 +
include/linux/swap.h | 10 ++++++----
mm/memcontrol.c | 19 ++++++++++++-------
mm/page_counter.c | 17 +++++++++++++++++
mm/shmem.c | 6 ++++--
mm/swapfile.c | 21 +++++++++++++++------
mm/vmscan.c | 7 +++++--
7 files changed, 60 insertions(+), 21 deletions(-)
--
2.47.3
^ permalink raw reply [flat|nested] 12+ messages in thread* [RFC PATCH v3 1/4] mm: add page_counter_margin() 2026-07-17 12:25 [RFC PATCH v3 0/4] mm: avoid large folio splits when swap is unavailable Xueyuan Chen @ 2026-07-17 12:25 ` Xueyuan Chen 2026-07-17 12:25 ` [RFC PATCH v3 2/4] mm: distinguish large folio swap allocation failures Xueyuan Chen ` (2 subsequent siblings) 3 siblings, 0 replies; 12+ messages in thread From: Xueyuan Chen @ 2026-07-17 12:25 UTC (permalink / raw) To: akpm, linux-mm Cc: linux-kernel, cgroups, baohua, zhaonanzhe, hannes, mhocko, roman.gushchin, shakeel.butt, muchun.song, chrisl, kasong, shikemeng, nphamcs, bhe, youngjun.park, david, ljs, liam, vbabka, rppt, surenb, qi.zheng, axelrasmussen, yuanchu, weixugc, baolin.wang, hughd, Xueyuan Chen From: Johannes Weiner <hannes@cmpxchg.org> mem_cgroup_get_nr_swap_pages() open-codes the remaining capacity across the memcg swap counter hierarchy. Add page_counter_margin() to return the minimum usable space from a page counter to the root, and use it in mem_cgroup_get_nr_swap_pages(). This is a pure refactoring with no intended behavior change. Signed-off-by: Johannes Weiner <hannes@cmpxchg.org> Signed-off-by: Xueyuan Chen <xueyuan.chen21@gmail.com> --- include/linux/page_counter.h | 1 + mm/memcontrol.c | 9 +++------ mm/page_counter.c | 17 +++++++++++++++++ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/include/linux/page_counter.h b/include/linux/page_counter.h index d649b6bbbc87..07b7cb12249c 100644 --- a/include/linux/page_counter.h +++ b/include/linux/page_counter.h @@ -68,6 +68,7 @@ static inline unsigned long page_counter_read(struct page_counter *counter) return atomic_long_read(&counter->usage); } +long page_counter_margin(struct page_counter *counter); void page_counter_cancel(struct page_counter *counter, unsigned long nr_pages); void page_counter_charge(struct page_counter *counter, unsigned long nr_pages); bool page_counter_try_charge(struct page_counter *counter, diff --git a/mm/memcontrol.c b/mm/memcontrol.c index 177732fef010..1e10f493d2a9 100644 --- a/mm/memcontrol.c +++ b/mm/memcontrol.c @@ -5548,12 +5548,9 @@ long mem_cgroup_get_nr_swap_pages(struct mem_cgroup *memcg) { long nr_swap_pages = get_nr_swap_pages(); - if (mem_cgroup_disabled() || do_memsw_account()) - return nr_swap_pages; - for (; !mem_cgroup_is_root(memcg); memcg = parent_mem_cgroup(memcg)) - nr_swap_pages = min_t(long, nr_swap_pages, - READ_ONCE(memcg->swap.max) - - page_counter_read(&memcg->swap)); + if (!mem_cgroup_disabled() && !do_memsw_account()) + nr_swap_pages = min(nr_swap_pages, page_counter_margin(&memcg->swap)); + return nr_swap_pages; } diff --git a/mm/page_counter.c b/mm/page_counter.c index 661e0f2a5127..7300dca90632 100644 --- a/mm/page_counter.c +++ b/mm/page_counter.c @@ -46,6 +46,23 @@ static void propagate_protected_usage(struct page_counter *c, } } +/** + * page_counter_margin - remaining usable space within hierarchical limits + * @counter: counter + */ +long page_counter_margin(struct page_counter *counter) +{ + long margin = PAGE_COUNTER_MAX; + + do { + long m = READ_ONCE(counter->max) - page_counter_read(counter); + + margin = min(margin, m); + } while ((counter = counter->parent)); + + return margin; +} + /** * page_counter_cancel - take pages out of the local counter * @counter: counter -- 2.47.3 ^ permalink raw reply related [flat|nested] 12+ messages in thread
* [RFC PATCH v3 2/4] mm: distinguish large folio swap allocation failures 2026-07-17 12:25 [RFC PATCH v3 0/4] mm: avoid large folio splits when swap is unavailable Xueyuan Chen 2026-07-17 12:25 ` [RFC PATCH v3 1/4] mm: add page_counter_margin() Xueyuan Chen @ 2026-07-17 12:25 ` Xueyuan Chen 2026-07-21 8:15 ` Barry Song 2026-07-21 16:13 ` Youngjun Park 2026-07-17 12:25 ` [RFC PATCH v3 3/4] mm/vmscan: avoid pointless large folio splits without swap Xueyuan Chen 2026-07-17 12:25 ` [RFC PATCH v3 4/4] mm/shmem: split large folios only on -E2BIG Xueyuan Chen 3 siblings, 2 replies; 12+ messages in thread From: Xueyuan Chen @ 2026-07-17 12:25 UTC (permalink / raw) To: akpm, linux-mm Cc: linux-kernel, cgroups, baohua, zhaonanzhe, hannes, mhocko, roman.gushchin, shakeel.butt, muchun.song, chrisl, kasong, shikemeng, nphamcs, bhe, youngjun.park, david, ljs, liam, vbabka, rppt, surenb, qi.zheng, axelrasmussen, yuanchu, weixugc, baolin.wang, hughd, Xueyuan Chen folio_alloc_swap() reports most allocation failures with a generic negative error code. Reclaim cannot tell whether splitting a large folio could make progress or whether there is no backing space at all. Track the global free swap count around the allocation attempt and let the memcg swap charge path cap it by the remaining hierarchical swap margin. Return -E2BIG for large folios when a smaller allocation might still fit, -ENOSPC when no swap space is available, and -ENOMEM when the failure is not helped by splitting. This only refines folio_alloc_swap() return codes. The reclaim caller is updated separately. Signed-off-by: Xueyuan Chen <xueyuan.chen21@gmail.com> --- include/linux/swap.h | 10 ++++++---- mm/memcontrol.c | 10 +++++++++- mm/swapfile.c | 21 +++++++++++++++------ 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/include/linux/swap.h b/include/linux/swap.h index 7a09df6977a5..0695ac56457f 100644 --- a/include/linux/swap.h +++ b/include/linux/swap.h @@ -571,13 +571,14 @@ static inline void folio_throttle_swaprate(struct folio *folio, gfp_t gfp) #endif #if defined(CONFIG_MEMCG) && defined(CONFIG_SWAP) -int __mem_cgroup_try_charge_swap(struct folio *folio, swp_entry_t entry); +int __mem_cgroup_try_charge_swap(struct folio *folio, swp_entry_t entry, + long *nr_swap_pages); static inline int mem_cgroup_try_charge_swap(struct folio *folio, - swp_entry_t entry) + swp_entry_t entry, long *nr_swap_pages) { if (mem_cgroup_disabled()) return 0; - return __mem_cgroup_try_charge_swap(folio, entry); + return __mem_cgroup_try_charge_swap(folio, entry, nr_swap_pages); } extern void __mem_cgroup_uncharge_swap(swp_entry_t entry, unsigned int nr_pages); @@ -592,7 +593,8 @@ extern long mem_cgroup_get_nr_swap_pages(struct mem_cgroup *memcg); extern bool mem_cgroup_swap_full(struct folio *folio); #else static inline int mem_cgroup_try_charge_swap(struct folio *folio, - swp_entry_t entry) + swp_entry_t entry, + long *nr_swap_pages) { return 0; } diff --git a/mm/memcontrol.c b/mm/memcontrol.c index 1e10f493d2a9..7c9e7072fb39 100644 --- a/mm/memcontrol.c +++ b/mm/memcontrol.c @@ -5472,12 +5472,14 @@ int __init mem_cgroup_init(void) * __mem_cgroup_try_charge_swap - try charging swap space for a folio * @folio: folio being added to swap * @entry: swap entry to charge + * @nr_swap_pages: optional swap availability to cap by memcg margin * * Try to charge @folio's memcg for the swap space at @entry. * * Returns 0 on success, -ENOMEM on failure. */ -int __mem_cgroup_try_charge_swap(struct folio *folio, swp_entry_t entry) +int __mem_cgroup_try_charge_swap(struct folio *folio, swp_entry_t entry, + long *nr_swap_pages) { unsigned int nr_pages = folio_nr_pages(folio); struct page_counter *counter; @@ -5495,6 +5497,9 @@ int __mem_cgroup_try_charge_swap(struct folio *folio, swp_entry_t entry) rcu_read_lock(); memcg = obj_cgroup_memcg(objcg); if (!entry.val) { + if (nr_swap_pages && !mem_cgroup_is_root(memcg)) + *nr_swap_pages = min(*nr_swap_pages, + page_counter_margin(&memcg->swap)); memcg_memory_event(memcg, MEMCG_SWAP_FAIL); rcu_read_unlock(); return 0; @@ -5509,6 +5514,9 @@ int __mem_cgroup_try_charge_swap(struct folio *folio, swp_entry_t entry) memcg_memory_event(memcg, MEMCG_SWAP_MAX); memcg_memory_event(memcg, MEMCG_SWAP_FAIL); mem_cgroup_private_id_put(memcg, nr_pages); + if (nr_swap_pages) + *nr_swap_pages = min(*nr_swap_pages, + page_counter_margin(counter)); return -ENOMEM; } mod_memcg_state(memcg, MEMCG_SWAP, nr_pages); diff --git a/mm/swapfile.c b/mm/swapfile.c index 9174f1eeffb0..53a921ca099a 100644 --- a/mm/swapfile.c +++ b/mm/swapfile.c @@ -1690,12 +1690,14 @@ static int swap_dup_entries_cluster(struct swap_info_struct *si, * swap cache. * * Context: Caller needs to hold the folio lock. - * Return: Whether the folio was added to the swap cache. + * Return: 0 on success, -E2BIG if splitting the folio might allow swapout, + * or another negative error code if splitting would not help. */ int folio_alloc_swap(struct folio *folio) { unsigned int order = folio_order(folio); unsigned int size = 1 << order; + long nr_swap_pages; VM_BUG_ON_FOLIO(!folio_test_locked(folio), folio); VM_BUG_ON_FOLIO(!folio_test_uptodate(folio), folio); @@ -1706,7 +1708,7 @@ int folio_alloc_swap(struct folio *folio) * the caller should split the folio and try again. */ if (!IS_ENABLED(CONFIG_THP_SWAP)) - return -EAGAIN; + return -E2BIG; /* * Allocation size should never exceed cluster size @@ -1714,10 +1716,12 @@ int folio_alloc_swap(struct folio *folio) */ if (size > SWAPFILE_CLUSTER) { VM_WARN_ON_ONCE(1); - return -EINVAL; + return -E2BIG; } } + nr_swap_pages = get_nr_swap_pages(); + again: local_lock(&percpu_swap_cluster.lock); if (!swap_alloc_fast(folio)) @@ -1730,11 +1734,16 @@ 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, folio->swap))) + if (unlikely(mem_cgroup_try_charge_swap(folio, folio->swap, + &nr_swap_pages))) { swap_cache_del_folio(folio); + return order && nr_swap_pages > 0 ? -E2BIG : -ENOMEM; + } - if (unlikely(!folio_test_swapcache(folio))) - return -ENOMEM; + if (unlikely(!folio_test_swapcache(folio))) { + nr_swap_pages = get_nr_swap_pages(); + return order && nr_swap_pages > 0 ? -E2BIG : -ENOSPC; + } return 0; } -- 2.47.3 ^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [RFC PATCH v3 2/4] mm: distinguish large folio swap allocation failures 2026-07-17 12:25 ` [RFC PATCH v3 2/4] mm: distinguish large folio swap allocation failures Xueyuan Chen @ 2026-07-21 8:15 ` Barry Song 2026-07-21 11:53 ` Johannes Weiner 2026-07-23 14:49 ` Xueyuan Chen 2026-07-21 16:13 ` Youngjun Park 1 sibling, 2 replies; 12+ messages in thread From: Barry Song @ 2026-07-21 8:15 UTC (permalink / raw) To: Xueyuan Chen Cc: akpm, linux-mm, linux-kernel, cgroups, zhaonanzhe, hannes, mhocko, roman.gushchin, shakeel.butt, muchun.song, chrisl, kasong, shikemeng, nphamcs, bhe, youngjun.park, david, ljs, liam, vbabka, rppt, surenb, qi.zheng, axelrasmussen, yuanchu, weixugc, baolin.wang, hughd On Fri, Jul 17, 2026 at 8:25 PM Xueyuan Chen <xueyuan.chen21@gmail.com> wrote: > > folio_alloc_swap() reports most allocation failures with a generic > negative error code. Reclaim cannot tell whether splitting a large folio > could make progress or whether there is no backing space at all. > > Track the global free swap count around the allocation attempt and let the > memcg swap charge path cap it by the remaining hierarchical swap margin. > Return -E2BIG for large folios when a smaller allocation might still fit, > -ENOSPC when no swap space is available, and -ENOMEM when the failure is > not helped by splitting. > > This only refines folio_alloc_swap() return codes. The reclaim caller is > updated separately. > > Signed-off-by: Xueyuan Chen <xueyuan.chen21@gmail.com> > --- > include/linux/swap.h | 10 ++++++---- > mm/memcontrol.c | 10 +++++++++- > mm/swapfile.c | 21 +++++++++++++++------ > 3 files changed, 30 insertions(+), 11 deletions(-) > > diff --git a/include/linux/swap.h b/include/linux/swap.h > index 7a09df6977a5..0695ac56457f 100644 > --- a/include/linux/swap.h > +++ b/include/linux/swap.h > @@ -571,13 +571,14 @@ static inline void folio_throttle_swaprate(struct folio *folio, gfp_t gfp) > #endif > > #if defined(CONFIG_MEMCG) && defined(CONFIG_SWAP) > -int __mem_cgroup_try_charge_swap(struct folio *folio, swp_entry_t entry); > +int __mem_cgroup_try_charge_swap(struct folio *folio, swp_entry_t entry, > + long *nr_swap_pages); > static inline int mem_cgroup_try_charge_swap(struct folio *folio, > - swp_entry_t entry) > + swp_entry_t entry, long *nr_swap_pages) > { > if (mem_cgroup_disabled()) > return 0; > - return __mem_cgroup_try_charge_swap(folio, entry); > + return __mem_cgroup_try_charge_swap(folio, entry, nr_swap_pages); > } > > extern void __mem_cgroup_uncharge_swap(swp_entry_t entry, unsigned int nr_pages); > @@ -592,7 +593,8 @@ extern long mem_cgroup_get_nr_swap_pages(struct mem_cgroup *memcg); > extern bool mem_cgroup_swap_full(struct folio *folio); > #else > static inline int mem_cgroup_try_charge_swap(struct folio *folio, > - swp_entry_t entry) > + swp_entry_t entry, > + long *nr_swap_pages) > { > return 0; > } > diff --git a/mm/memcontrol.c b/mm/memcontrol.c > index 1e10f493d2a9..7c9e7072fb39 100644 > --- a/mm/memcontrol.c > +++ b/mm/memcontrol.c > @@ -5472,12 +5472,14 @@ int __init mem_cgroup_init(void) > * __mem_cgroup_try_charge_swap - try charging swap space for a folio > * @folio: folio being added to swap > * @entry: swap entry to charge > + * @nr_swap_pages: optional swap availability to cap by memcg margin > * > * Try to charge @folio's memcg for the swap space at @entry. > * > * Returns 0 on success, -ENOMEM on failure. > */ > -int __mem_cgroup_try_charge_swap(struct folio *folio, swp_entry_t entry) > +int __mem_cgroup_try_charge_swap(struct folio *folio, swp_entry_t entry, > + long *nr_swap_pages) > { > unsigned int nr_pages = folio_nr_pages(folio); > struct page_counter *counter; > @@ -5495,6 +5497,9 @@ int __mem_cgroup_try_charge_swap(struct folio *folio, swp_entry_t entry) > rcu_read_lock(); > memcg = obj_cgroup_memcg(objcg); > if (!entry.val) { > + if (nr_swap_pages && !mem_cgroup_is_root(memcg)) > + *nr_swap_pages = min(*nr_swap_pages, > + page_counter_margin(&memcg->swap)); This looks a bit odd. Could we decouple *nr_swap_pages from margin and have the function always return margin instead? I don't think this function needs to care about the initial value returned by get_nr_swap_pages(). While folio_alloc_swap() may depend on it, they're different software layers, and we shouldn't couple them together. > memcg_memory_event(memcg, MEMCG_SWAP_FAIL); > rcu_read_unlock(); > return 0; > @@ -5509,6 +5514,9 @@ int __mem_cgroup_try_charge_swap(struct folio *folio, swp_entry_t entry) > memcg_memory_event(memcg, MEMCG_SWAP_MAX); > memcg_memory_event(memcg, MEMCG_SWAP_FAIL); > mem_cgroup_private_id_put(memcg, nr_pages); > + if (nr_swap_pages) > + *nr_swap_pages = min(*nr_swap_pages, > + page_counter_margin(counter)); > return -ENOMEM; > } > mod_memcg_state(memcg, MEMCG_SWAP, nr_pages); > diff --git a/mm/swapfile.c b/mm/swapfile.c > index 9174f1eeffb0..53a921ca099a 100644 > --- a/mm/swapfile.c > +++ b/mm/swapfile.c > @@ -1690,12 +1690,14 @@ static int swap_dup_entries_cluster(struct swap_info_struct *si, > * swap cache. > * > * Context: Caller needs to hold the folio lock. > - * Return: Whether the folio was added to the swap cache. > + * Return: 0 on success, -E2BIG if splitting the folio might allow swapout, > + * or another negative error code if splitting would not help. > */ > int folio_alloc_swap(struct folio *folio) > { > unsigned int order = folio_order(folio); > unsigned int size = 1 << order; > + long nr_swap_pages; > > VM_BUG_ON_FOLIO(!folio_test_locked(folio), folio); > VM_BUG_ON_FOLIO(!folio_test_uptodate(folio), folio); > @@ -1706,7 +1708,7 @@ int folio_alloc_swap(struct folio *folio) > * the caller should split the folio and try again. > */ > if (!IS_ENABLED(CONFIG_THP_SWAP)) > - return -EAGAIN; > + return -E2BIG; We could also have the case where we're running out of swap space and it's not worth splitting, even when THP_SWAP is disabled. > > /* > * Allocation size should never exceed cluster size > @@ -1714,10 +1716,12 @@ int folio_alloc_swap(struct folio *folio) > */ > if (size > SWAPFILE_CLUSTER) { > VM_WARN_ON_ONCE(1); > - return -EINVAL; > + return -E2BIG; Similar to the above. > } > } > > + nr_swap_pages = get_nr_swap_pages(); > + > again: > local_lock(&percpu_swap_cluster.lock); > if (!swap_alloc_fast(folio)) > @@ -1730,11 +1734,16 @@ 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, folio->swap))) > + if (unlikely(mem_cgroup_try_charge_swap(folio, folio->swap, > + &nr_swap_pages))) { As explained above, we shouldn't couple two different software layers. don't use nr_swap_pages() as the initial value of memcg charge. Best Regards Barry ^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [RFC PATCH v3 2/4] mm: distinguish large folio swap allocation failures 2026-07-21 8:15 ` Barry Song @ 2026-07-21 11:53 ` Johannes Weiner 2026-07-23 14:49 ` Xueyuan Chen 1 sibling, 0 replies; 12+ messages in thread From: Johannes Weiner @ 2026-07-21 11:53 UTC (permalink / raw) To: Barry Song Cc: Xueyuan Chen, akpm, linux-mm, linux-kernel, cgroups, zhaonanzhe, mhocko, roman.gushchin, shakeel.butt, muchun.song, chrisl, kasong, shikemeng, nphamcs, bhe, youngjun.park, david, ljs, liam, vbabka, rppt, surenb, qi.zheng, axelrasmussen, yuanchu, weixugc, baolin.wang, hughd On Tue, Jul 21, 2026 at 04:15:13PM +0800, Barry Song wrote: > On Fri, Jul 17, 2026 at 8:25 PM Xueyuan Chen <xueyuan.chen21@gmail.com> wrote: > > @@ -5495,6 +5497,9 @@ int __mem_cgroup_try_charge_swap(struct folio *folio, swp_entry_t entry) > > rcu_read_lock(); > > memcg = obj_cgroup_memcg(objcg); > > if (!entry.val) { > > + if (nr_swap_pages && !mem_cgroup_is_root(memcg)) > > + *nr_swap_pages = min(*nr_swap_pages, > > + page_counter_margin(&memcg->swap)); > > This looks a bit odd. Could we decouple *nr_swap_pages from margin and > have the function always return margin instead? > > I don't think this function needs to care about the initial value returned by > get_nr_swap_pages(). While folio_alloc_swap() may depend on it, they're > different software layers, and we shouldn't couple them together. Agreed! ^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [RFC PATCH v3 2/4] mm: distinguish large folio swap allocation failures 2026-07-21 8:15 ` Barry Song 2026-07-21 11:53 ` Johannes Weiner @ 2026-07-23 14:49 ` Xueyuan Chen 1 sibling, 0 replies; 12+ messages in thread From: Xueyuan Chen @ 2026-07-23 14:49 UTC (permalink / raw) To: baohua Cc: xueyuan.chen21, akpm, linux-mm, linux-kernel, cgroups, zhaonanzhe, hannes, mhocko, roman.gushchin, shakeel.butt, muchun.song, chrisl, kasong, shikemeng, nphamcs, bhe, youngjun.park, david, ljs, liam, vbabka, rppt, surenb, qi.zheng, axelrasmussen, yuanchu, weixugc, baolin.wang, hughd On Tue, Jul 21, 2026 at 04:15:13PM +0800, Barry Song wrote: >On Fri, Jul 17, 2026 at 8:25 PM Xueyuan Chen <xueyuan.chen21@gmail.com> wrote: >> >> folio_alloc_swap() reports most allocation failures with a generic >> negative error code. Reclaim cannot tell whether splitting a large folio >> could make progress or whether there is no backing space at all. >> >> Track the global free swap count around the allocation attempt and let the >> memcg swap charge path cap it by the remaining hierarchical swap margin. >> Return -E2BIG for large folios when a smaller allocation might still fit, >> -ENOSPC when no swap space is available, and -ENOMEM when the failure is >> not helped by splitting. >> >> This only refines folio_alloc_swap() return codes. The reclaim caller is >> updated separately. >> >> Signed-off-by: Xueyuan Chen <xueyuan.chen21@gmail.com> >> --- >> include/linux/swap.h | 10 ++++++---- >> mm/memcontrol.c | 10 +++++++++- >> mm/swapfile.c | 21 +++++++++++++++------ >> 3 files changed, 30 insertions(+), 11 deletions(-) >> >> diff --git a/include/linux/swap.h b/include/linux/swap.h >> index 7a09df6977a5..0695ac56457f 100644 >> --- a/include/linux/swap.h >> +++ b/include/linux/swap.h >> @@ -571,13 +571,14 @@ static inline void folio_throttle_swaprate(struct folio *folio, gfp_t gfp) >> #endif >> >> #if defined(CONFIG_MEMCG) && defined(CONFIG_SWAP) >> -int __mem_cgroup_try_charge_swap(struct folio *folio, swp_entry_t entry); >> +int __mem_cgroup_try_charge_swap(struct folio *folio, swp_entry_t entry, >> + long *nr_swap_pages); >> static inline int mem_cgroup_try_charge_swap(struct folio *folio, >> - swp_entry_t entry) >> + swp_entry_t entry, long *nr_swap_pages) >> { >> if (mem_cgroup_disabled()) >> return 0; >> - return __mem_cgroup_try_charge_swap(folio, entry); >> + return __mem_cgroup_try_charge_swap(folio, entry, nr_swap_pages); >> } >> >> extern void __mem_cgroup_uncharge_swap(swp_entry_t entry, unsigned int nr_pages); >> @@ -592,7 +593,8 @@ extern long mem_cgroup_get_nr_swap_pages(struct mem_cgroup *memcg); >> extern bool mem_cgroup_swap_full(struct folio *folio); >> #else >> static inline int mem_cgroup_try_charge_swap(struct folio *folio, >> - swp_entry_t entry) >> + swp_entry_t entry, >> + long *nr_swap_pages) >> { >> return 0; >> } >> diff --git a/mm/memcontrol.c b/mm/memcontrol.c >> index 1e10f493d2a9..7c9e7072fb39 100644 >> --- a/mm/memcontrol.c >> +++ b/mm/memcontrol.c >> @@ -5472,12 +5472,14 @@ int __init mem_cgroup_init(void) >> * __mem_cgroup_try_charge_swap - try charging swap space for a folio >> * @folio: folio being added to swap >> * @entry: swap entry to charge >> + * @nr_swap_pages: optional swap availability to cap by memcg margin >> * >> * Try to charge @folio's memcg for the swap space at @entry. >> * >> * Returns 0 on success, -ENOMEM on failure. >> */ >> -int __mem_cgroup_try_charge_swap(struct folio *folio, swp_entry_t entry) >> +int __mem_cgroup_try_charge_swap(struct folio *folio, swp_entry_t entry, >> + long *nr_swap_pages) >> { >> unsigned int nr_pages = folio_nr_pages(folio); >> struct page_counter *counter; >> @@ -5495,6 +5497,9 @@ int __mem_cgroup_try_charge_swap(struct folio *folio, swp_entry_t entry) >> rcu_read_lock(); >> memcg = obj_cgroup_memcg(objcg); >> if (!entry.val) { >> + if (nr_swap_pages && !mem_cgroup_is_root(memcg)) >> + *nr_swap_pages = min(*nr_swap_pages, >> + page_counter_margin(&memcg->swap)); > >This looks a bit odd. Could we decouple *nr_swap_pages from margin and >have the function always return margin instead? > >I don't think this function needs to care about the initial value returned by >get_nr_swap_pages(). While folio_alloc_swap() may depend on it, they're >different software layers, and we shouldn't couple them together. > > >> memcg_memory_event(memcg, MEMCG_SWAP_FAIL); >> rcu_read_unlock(); >> return 0; >> @@ -5509,6 +5514,9 @@ int __mem_cgroup_try_charge_swap(struct folio *folio, swp_entry_t entry) >> memcg_memory_event(memcg, MEMCG_SWAP_MAX); >> memcg_memory_event(memcg, MEMCG_SWAP_FAIL); >> mem_cgroup_private_id_put(memcg, nr_pages); >> + if (nr_swap_pages) >> + *nr_swap_pages = min(*nr_swap_pages, >> + page_counter_margin(counter)); >> return -ENOMEM; >> } >> mod_memcg_state(memcg, MEMCG_SWAP, nr_pages); >> diff --git a/mm/swapfile.c b/mm/swapfile.c >> index 9174f1eeffb0..53a921ca099a 100644 >> --- a/mm/swapfile.c >> +++ b/mm/swapfile.c >> @@ -1690,12 +1690,14 @@ static int swap_dup_entries_cluster(struct swap_info_struct *si, >> * swap cache. >> * >> * Context: Caller needs to hold the folio lock. >> - * Return: Whether the folio was added to the swap cache. >> + * Return: 0 on success, -E2BIG if splitting the folio might allow swapout, >> + * or another negative error code if splitting would not help. >> */ >> int folio_alloc_swap(struct folio *folio) >> { >> unsigned int order = folio_order(folio); >> unsigned int size = 1 << order; >> + long nr_swap_pages; >> >> VM_BUG_ON_FOLIO(!folio_test_locked(folio), folio); >> VM_BUG_ON_FOLIO(!folio_test_uptodate(folio), folio); >> @@ -1706,7 +1708,7 @@ int folio_alloc_swap(struct folio *folio) >> * the caller should split the folio and try again. >> */ >> if (!IS_ENABLED(CONFIG_THP_SWAP)) >> - return -EAGAIN; >> + return -E2BIG; > >We could also have the case where we're running out of swap space and it's not >worth splitting, even when THP_SWAP is disabled. > >> >> /* >> * Allocation size should never exceed cluster size >> @@ -1714,10 +1716,12 @@ int folio_alloc_swap(struct folio *folio) >> */ >> if (size > SWAPFILE_CLUSTER) { >> VM_WARN_ON_ONCE(1); >> - return -EINVAL; >> + return -E2BIG; > >Similar to the above. > >> } >> } >> >> + nr_swap_pages = get_nr_swap_pages(); >> + >> again: >> local_lock(&percpu_swap_cluster.lock); >> if (!swap_alloc_fast(folio)) >> @@ -1730,11 +1734,16 @@ 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, folio->swap))) >> + if (unlikely(mem_cgroup_try_charge_swap(folio, folio->swap, >> + &nr_swap_pages))) { > >As explained above, we shouldn't couple two different software layers. >don't use nr_swap_pages() as the initial value of memcg charge. > >Best Regards >Barry > Thank you very much for the careful review, Barry. You're right that passing the value from get_nr_swap_pages() into the memcg charge path unnecessarily couples two separate layers. How about the patch below? This version keeps global swap availability and the memcg swap margin separate. When swap allocation or charging fails, the memcg charge path reports only the remaining margin in its own hierarchy. folio_alloc_swap() checks global swap availability separately and uses the two results to select the return code. If swap slot allocation succeeds but the memcg charge fails, the global allocation has already succeeded. In that case, folio_alloc_swap() uses the remaining memcg margin alone to choose between -E2BIG and -ENOMEM. The early rejection paths now return -E2BIG only when splitting may still make progress, addressing the cases you pointed out for CONFIG_THP_SWAP=n and size > SWAPFILE_CLUSTER. I added mem_cgroup_get_folio_swap_margin() to return the minimum remaining swap capacity across the memcg hierarchy associated with a folio. Unlike mem_cgroup_get_nr_swap_pages(), it reports only the memcg constraint and does not combine it with the global free swap count. I also added folio_alloc_swap_error() to avoid duplicating the checks that decide whether to return -ENOSPC, -ENOMEM, or -E2BIG. Do you think this helper is worthwhile, or would it be clearer to keep these checks directly in folio_alloc_swap()? Thanks again for pointing this out. The updated patch follows: include/linux/swap.h | 16 ++++++++++++---- mm/memcontrol.c | 33 ++++++++++++++++++++++++++++++++- mm/swapfile.c | 31 ++++++++++++++++++++++++------- 3 files changed, 68 insertions(+), 12 deletions(-) diff --git a/include/linux/swap.h b/include/linux/swap.h index 7a09df6977a5..87c58f69d11d 100644 --- a/include/linux/swap.h +++ b/include/linux/swap.h @@ -571,13 +571,14 @@ static inline void folio_throttle_swaprate(struct folio *folio, gfp_t gfp) #endif #if defined(CONFIG_MEMCG) && defined(CONFIG_SWAP) -int __mem_cgroup_try_charge_swap(struct folio *folio, swp_entry_t entry); +int __mem_cgroup_try_charge_swap(struct folio *folio, swp_entry_t entry, + long *swap_margin); static inline int mem_cgroup_try_charge_swap(struct folio *folio, - swp_entry_t entry) + swp_entry_t entry, long *swap_margin) { if (mem_cgroup_disabled()) return 0; - return __mem_cgroup_try_charge_swap(folio, entry); + return __mem_cgroup_try_charge_swap(folio, entry, swap_margin); } extern void __mem_cgroup_uncharge_swap(swp_entry_t entry, unsigned int nr_pages); @@ -588,11 +589,13 @@ static inline void mem_cgroup_uncharge_swap(swp_entry_t entry, unsigned int nr_p __mem_cgroup_uncharge_swap(entry, nr_pages); } +long mem_cgroup_get_folio_swap_margin(struct folio *folio); extern long mem_cgroup_get_nr_swap_pages(struct mem_cgroup *memcg); extern bool mem_cgroup_swap_full(struct folio *folio); #else static inline int mem_cgroup_try_charge_swap(struct folio *folio, - swp_entry_t entry) + swp_entry_t entry, + long *swap_margin) { return 0; } @@ -602,6 +605,11 @@ static inline void mem_cgroup_uncharge_swap(swp_entry_t entry, { } +static inline long mem_cgroup_get_folio_swap_margin(struct folio *folio) +{ + return PAGE_COUNTER_MAX; +} + static inline long mem_cgroup_get_nr_swap_pages(struct mem_cgroup *memcg) { return get_nr_swap_pages(); diff --git a/mm/memcontrol.c b/mm/memcontrol.c index 1e10f493d2a9..0ebbbaffb522 100644 --- a/mm/memcontrol.c +++ b/mm/memcontrol.c @@ -5472,12 +5472,14 @@ int __init mem_cgroup_init(void) * __mem_cgroup_try_charge_swap - try charging swap space for a folio * @folio: folio being added to swap * @entry: swap entry to charge + * @swap_margin: remaining memcg swap margin if allocation or charge fails * * Try to charge @folio's memcg for the swap space at @entry. * * Returns 0 on success, -ENOMEM on failure. */ -int __mem_cgroup_try_charge_swap(struct folio *folio, swp_entry_t entry) +int __mem_cgroup_try_charge_swap(struct folio *folio, swp_entry_t entry, + long *swap_margin) { unsigned int nr_pages = folio_nr_pages(folio); struct page_counter *counter; @@ -5495,6 +5497,7 @@ int __mem_cgroup_try_charge_swap(struct folio *folio, swp_entry_t entry) rcu_read_lock(); memcg = obj_cgroup_memcg(objcg); if (!entry.val) { + *swap_margin = page_counter_margin(&memcg->swap); memcg_memory_event(memcg, MEMCG_SWAP_FAIL); rcu_read_unlock(); return 0; @@ -5509,6 +5512,7 @@ int __mem_cgroup_try_charge_swap(struct folio *folio, swp_entry_t entry) memcg_memory_event(memcg, MEMCG_SWAP_MAX); memcg_memory_event(memcg, MEMCG_SWAP_FAIL); mem_cgroup_private_id_put(memcg, nr_pages); + *swap_margin = page_counter_margin(counter); return -ENOMEM; } mod_memcg_state(memcg, MEMCG_SWAP, nr_pages); @@ -5554,6 +5558,33 @@ long mem_cgroup_get_nr_swap_pages(struct mem_cgroup *memcg) return nr_swap_pages; } +/** + * mem_cgroup_get_folio_swap_margin - get a folio's memcg swap margin + * @folio: folio whose memcg margin is queried + * + * Return: Remaining chargeable pages in the folio's memcg hierarchy. + */ +long mem_cgroup_get_folio_swap_margin(struct folio *folio) +{ + long swap_margin = PAGE_COUNTER_MAX; + struct mem_cgroup *memcg; + struct obj_cgroup *objcg; + + if (mem_cgroup_disabled() || do_memsw_account()) + return swap_margin; + + objcg = folio_objcg(folio); + if (!objcg) + return swap_margin; + + rcu_read_lock(); + memcg = obj_cgroup_memcg(objcg); + swap_margin = page_counter_margin(&memcg->swap); + rcu_read_unlock(); + + return swap_margin; +} + bool mem_cgroup_swap_full(struct folio *folio) { struct mem_cgroup *memcg; diff --git a/mm/swapfile.c b/mm/swapfile.c index 9174f1eeffb0..f90ee2de53fa 100644 --- a/mm/swapfile.c +++ b/mm/swapfile.c @@ -1682,6 +1682,16 @@ static int swap_dup_entries_cluster(struct swap_info_struct *si, return err; } +static int folio_alloc_swap_error(struct folio *folio, long swap_margin) +{ + if (get_nr_swap_pages() <= 0) + return -ENOSPC; + if (swap_margin <= 0) + return -ENOMEM; + + return folio_test_large(folio) ? -E2BIG : -ENOMEM; +} + /** * folio_alloc_swap - allocate swap space for a folio * @folio: folio we want to move to swap @@ -1690,23 +1700,26 @@ static int swap_dup_entries_cluster(struct swap_info_struct *si, * swap cache. * * Context: Caller needs to hold the folio lock. - * Return: Whether the folio was added to the swap cache. + * Return: 0 on success, -E2BIG if splitting the folio might allow swapout, + * or another negative error code if splitting would not help. */ int folio_alloc_swap(struct folio *folio) { unsigned int order = folio_order(folio); unsigned int size = 1 << order; + long swap_margin = PAGE_COUNTER_MAX; VM_BUG_ON_FOLIO(!folio_test_locked(folio), folio); VM_BUG_ON_FOLIO(!folio_test_uptodate(folio), folio); if (order) { /* - * Reject large allocation when THP_SWAP is disabled, - * the caller should split the folio and try again. + * Reject large allocation when THP_SWAP is disabled. The error + * classifier decides whether the caller should split and retry. */ if (!IS_ENABLED(CONFIG_THP_SWAP)) - return -EAGAIN; + return folio_alloc_swap_error(folio, + mem_cgroup_get_folio_swap_margin(folio)); /* * Allocation size should never exceed cluster size @@ -1714,7 +1727,8 @@ int folio_alloc_swap(struct folio *folio) */ if (size > SWAPFILE_CLUSTER) { VM_WARN_ON_ONCE(1); - return -EINVAL; + return folio_alloc_swap_error(folio, + mem_cgroup_get_folio_swap_margin(folio)); } } @@ -1730,11 +1744,14 @@ 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, folio->swap))) + if (unlikely(mem_cgroup_try_charge_swap(folio, folio->swap, + &swap_margin))) { swap_cache_del_folio(folio); + return order && swap_margin > 0 ? -E2BIG : -ENOMEM; + } if (unlikely(!folio_test_swapcache(folio))) - return -ENOMEM; + return folio_alloc_swap_error(folio, swap_margin); return 0; } -- 2.47.3 ^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [RFC PATCH v3 2/4] mm: distinguish large folio swap allocation failures 2026-07-17 12:25 ` [RFC PATCH v3 2/4] mm: distinguish large folio swap allocation failures Xueyuan Chen 2026-07-21 8:15 ` Barry Song @ 2026-07-21 16:13 ` Youngjun Park 2026-07-23 15:15 ` Xueyuan Chen 1 sibling, 1 reply; 12+ messages in thread From: Youngjun Park @ 2026-07-21 16:13 UTC (permalink / raw) To: Xueyuan Chen Cc: akpm, linux-mm, linux-kernel, cgroups, baohua, zhaonanzhe, hannes, mhocko, roman.gushchin, shakeel.butt, muchun.song, chrisl, kasong, shikemeng, nphamcs, bhe, david, ljs, liam, vbabka, rppt, surenb, qi.zheng, axelrasmussen, yuanchu, weixugc, baolin.wang, hughd On Fri, Jul 17, 2026 at 08:25:12PM +0800, Xueyuan Chen wrote: ... > local_lock(&percpu_swap_cluster.lock); > if (!swap_alloc_fast(folio)) > @@ -1730,11 +1734,16 @@ 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, folio->swap))) > + if (unlikely(mem_cgroup_try_charge_swap(folio, folio->swap, > + &nr_swap_pages))) { > swap_cache_del_folio(folio); IMHO, If we get here the allocation succeeded and only the charge failed, so we already know global swap space exists. (And also swap_cache_del_folio right above even puts the slots back, so nr_swap_pages goes up again) Could we just take the margin and decide -E2BIG or -ENOMEM from that? Youngjun ^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [RFC PATCH v3 2/4] mm: distinguish large folio swap allocation failures 2026-07-21 16:13 ` Youngjun Park @ 2026-07-23 15:15 ` Xueyuan Chen 0 siblings, 0 replies; 12+ messages in thread From: Xueyuan Chen @ 2026-07-23 15:15 UTC (permalink / raw) To: youngjun.park Cc: xueyuan.chen21, akpm, linux-mm, linux-kernel, cgroups, baohua, zhaonanzhe, hannes, mhocko, roman.gushchin, shakeel.butt, muchun.song, chrisl, kasong, shikemeng, nphamcs, bhe, david, ljs, liam, vbabka, rppt, surenb, qi.zheng, axelrasmussen, yuanchu, weixugc, baolin.wang, hughd On Wed, Jul 22, 2026 at 01:13:09AM +0900, Youngjun Park wrote: >On Fri, Jul 17, 2026 at 08:25:12PM +0800, Xueyuan Chen wrote: >... >> local_lock(&percpu_swap_cluster.lock); >> if (!swap_alloc_fast(folio)) >> @@ -1730,11 +1734,16 @@ 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, folio->swap))) >> + if (unlikely(mem_cgroup_try_charge_swap(folio, folio->swap, >> + &nr_swap_pages))) { >> swap_cache_del_folio(folio); > >IMHO, >If we get here the allocation succeeded and only the charge failed, so >we already know global swap space exists. >(And also swap_cache_del_folio right above even puts the slots back, >so nr_swap_pages goes up again) > >Could we just take the margin and decide -E2BIG or -ENOMEM from that? > >Youngjun > Thank you for the review, Youngjun. You're right. When the memcg charge fails at this point, the swap slot allocation has already succeeded. After swap_cache_del_folio() releases those slots, re-reading the global free swap count may also include the slots that were just returned, so it is not useful for classifying the charge failure. I changed the charge failure path to use the remaining memcg margin alone: if (unlikely(mem_cgroup_try_charge_swap(folio, folio->swap, &swap_margin))) { swap_cache_del_folio(folio); return order && swap_margin > 0 ? -E2BIG : -ENOMEM; } The global free swap count is now considered separately when classifying swap allocation failures and the early rejection paths. Thanks for catching this. I will include the change in the next version. Thanks Xueyuan ^ permalink raw reply [flat|nested] 12+ messages in thread
* [RFC PATCH v3 3/4] mm/vmscan: avoid pointless large folio splits without swap 2026-07-17 12:25 [RFC PATCH v3 0/4] mm: avoid large folio splits when swap is unavailable Xueyuan Chen 2026-07-17 12:25 ` [RFC PATCH v3 1/4] mm: add page_counter_margin() Xueyuan Chen 2026-07-17 12:25 ` [RFC PATCH v3 2/4] mm: distinguish large folio swap allocation failures Xueyuan Chen @ 2026-07-17 12:25 ` Xueyuan Chen 2026-07-21 8:00 ` Barry Song 2026-07-17 12:25 ` [RFC PATCH v3 4/4] mm/shmem: split large folios only on -E2BIG Xueyuan Chen 3 siblings, 1 reply; 12+ messages in thread From: Xueyuan Chen @ 2026-07-17 12:25 UTC (permalink / raw) To: akpm, linux-mm Cc: linux-kernel, cgroups, baohua, zhaonanzhe, hannes, mhocko, roman.gushchin, shakeel.butt, muchun.song, chrisl, kasong, shikemeng, nphamcs, bhe, youngjun.park, david, ljs, liam, vbabka, rppt, surenb, qi.zheng, axelrasmussen, yuanchu, weixugc, baolin.wang, hughd From: "Barry Song (Xiaomi)" <baohua@kernel.org> When swap is disabled, exhausted, or unavailable due to memcg swap limits, splitting a large anonymous folio cannot make swapout progress. The fallback only destroys the large folio and inflates split statistics. Use -E2BIG from folio_alloc_swap() as the explicit signal that splitting the folio might allow swapout of smaller pieces. For other allocation failures, keep the existing activation path and avoid the split. This preserves the split fallback for fragmented or partially available swap, while avoiding it when there is no backing space for any part of the folio. Reported-by: Nanzhe Zhao <zhaonanzhe@xiaomi.com> Signed-off-by: Barry Song (Xiaomi) <baohua@kernel.org> --- mm/vmscan.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mm/vmscan.c b/mm/vmscan.c index bd1b1aa12581..40340a88f78e 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -1260,6 +1260,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)) @@ -1278,10 +1280,11 @@ 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)) { + ret = folio_alloc_swap(folio); + if (ret) { int __maybe_unused order = folio_order(folio); - if (!folio_test_large(folio)) + if (!folio_test_large(folio) || ret != -E2BIG) goto activate_locked_split; /* Fallback to swap normal pages */ if (split_folio_to_list(folio, folio_list)) -- 2.47.3 ^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [RFC PATCH v3 3/4] mm/vmscan: avoid pointless large folio splits without swap 2026-07-17 12:25 ` [RFC PATCH v3 3/4] mm/vmscan: avoid pointless large folio splits without swap Xueyuan Chen @ 2026-07-21 8:00 ` Barry Song 0 siblings, 0 replies; 12+ messages in thread From: Barry Song @ 2026-07-21 8:00 UTC (permalink / raw) To: Xueyuan Chen Cc: akpm, linux-mm, linux-kernel, cgroups, zhaonanzhe, hannes, mhocko, roman.gushchin, shakeel.butt, muchun.song, chrisl, kasong, shikemeng, nphamcs, bhe, youngjun.park, david, ljs, liam, vbabka, rppt, surenb, qi.zheng, axelrasmussen, yuanchu, weixugc, baolin.wang, hughd On Fri, Jul 17, 2026 at 8:25 PM Xueyuan Chen <xueyuan.chen21@gmail.com> wrote: > > From: "Barry Song (Xiaomi)" <baohua@kernel.org> > > When swap is disabled, exhausted, or unavailable due to memcg swap > limits, splitting a large anonymous folio cannot make swapout progress. > The fallback only destroys the large folio and inflates split statistics. > > Use -E2BIG from folio_alloc_swap() as the explicit signal that splitting > the folio might allow swapout of smaller pieces. For other allocation > failures, keep the existing activation path and avoid the split. > > This preserves the split fallback for fragmented or partially available > swap, while avoiding it when there is no backing space for any part of the > folio. > > Reported-by: Nanzhe Zhao <zhaonanzhe@xiaomi.com> > Signed-off-by: Barry Song (Xiaomi) <baohua@kernel.org> > --- > mm/vmscan.c | 7 +++++-- > 1 file changed, 5 insertions(+), 2 deletions(-) > > diff --git a/mm/vmscan.c b/mm/vmscan.c > index bd1b1aa12581..40340a88f78e 100644 > --- a/mm/vmscan.c > +++ b/mm/vmscan.c > @@ -1260,6 +1260,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)) > @@ -1278,10 +1280,11 @@ 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)) { > + ret = folio_alloc_swap(folio); > + if (ret) { > int __maybe_unused order = folio_order(folio); > > - if (!folio_test_large(folio)) > + if (!folio_test_large(folio) || ret != -E2BIG) I'd rather split it into two checks: if (!folio_test_large(folio)) goto activate_locked_split; if (ret != -E2BIG) goto activate_locked_split; This makes it clear that ret != -E2BIG is only relevant for the large folio case. > goto activate_locked_split; > /* Fallback to swap normal pages */ > if (split_folio_to_list(folio, folio_list)) > -- > 2.47.3 > Best Regards Barry ^ permalink raw reply [flat|nested] 12+ messages in thread
* [RFC PATCH v3 4/4] mm/shmem: split large folios only on -E2BIG 2026-07-17 12:25 [RFC PATCH v3 0/4] mm: avoid large folio splits when swap is unavailable Xueyuan Chen ` (2 preceding siblings ...) 2026-07-17 12:25 ` [RFC PATCH v3 3/4] mm/vmscan: avoid pointless large folio splits without swap Xueyuan Chen @ 2026-07-17 12:25 ` Xueyuan Chen 2026-07-21 7:57 ` Barry Song 3 siblings, 1 reply; 12+ messages in thread From: Xueyuan Chen @ 2026-07-17 12:25 UTC (permalink / raw) To: akpm, linux-mm Cc: linux-kernel, cgroups, baohua, zhaonanzhe, hannes, mhocko, roman.gushchin, shakeel.butt, muchun.song, chrisl, kasong, shikemeng, nphamcs, bhe, youngjun.park, david, ljs, liam, vbabka, rppt, surenb, qi.zheng, axelrasmussen, yuanchu, weixugc, baolin.wang, hughd, Xueyuan Chen shmem_writeout() currently splits a large folio on every folio_alloc_swap() failure. With the refined return-value contract, only -E2BIG indicates that splitting might allow smaller folios to be swapped out. Enter the split fallback only for -E2BIG. For -ENOSPC and -ENOMEM, redirty and reactivate the folio as before. Suggested-by: Baolin Wang <baolin.wang@linux.alibaba.com> Signed-off-by: Xueyuan Chen <xueyuan.chen21@gmail.com> --- mm/shmem.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mm/shmem.c b/mm/shmem.c index 3b5dc21b323c..d76812dd3cef 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -1599,6 +1599,7 @@ int shmem_writeout(struct folio *folio, struct swap_iocb **plug, struct shmem_sb_info *sbinfo = SHMEM_SB(inode->i_sb); pgoff_t index; int nr_pages; + int ret; bool split = false; if ((info->flags & SHMEM_F_LOCKED) || sbinfo->noswap) @@ -1679,7 +1680,8 @@ int shmem_writeout(struct folio *folio, struct swap_iocb **plug, folio_mark_uptodate(folio); } - if (!folio_alloc_swap(folio)) { + ret = folio_alloc_swap(folio); + if (!ret) { bool first_swapped = shmem_recalc_inode(inode, 0, nr_pages); int error; @@ -1732,7 +1734,7 @@ int shmem_writeout(struct folio *folio, struct swap_iocb **plug, swap_cache_del_folio(folio); goto redirty; } - if (nr_pages > 1) + if (ret == -E2BIG) goto try_split; redirty: folio_mark_dirty(folio); -- 2.47.3 ^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [RFC PATCH v3 4/4] mm/shmem: split large folios only on -E2BIG 2026-07-17 12:25 ` [RFC PATCH v3 4/4] mm/shmem: split large folios only on -E2BIG Xueyuan Chen @ 2026-07-21 7:57 ` Barry Song 0 siblings, 0 replies; 12+ messages in thread From: Barry Song @ 2026-07-21 7:57 UTC (permalink / raw) To: Xueyuan Chen Cc: akpm, linux-mm, linux-kernel, cgroups, zhaonanzhe, hannes, mhocko, roman.gushchin, shakeel.butt, muchun.song, chrisl, kasong, shikemeng, nphamcs, bhe, youngjun.park, david, ljs, liam, vbabka, rppt, surenb, qi.zheng, axelrasmussen, yuanchu, weixugc, baolin.wang, hughd On Fri, Jul 17, 2026 at 8:26 PM Xueyuan Chen <xueyuan.chen21@gmail.com> wrote: > > shmem_writeout() currently splits a large folio on every > folio_alloc_swap() failure. With the refined return-value contract, only > -E2BIG indicates that splitting might allow smaller folios to be swapped > out. > > Enter the split fallback only for -E2BIG. For -ENOSPC and -ENOMEM, > redirty and reactivate the folio as before. > > Suggested-by: Baolin Wang <baolin.wang@linux.alibaba.com> > Signed-off-by: Xueyuan Chen <xueyuan.chen21@gmail.com> > --- > mm/shmem.c | 6 ++++-- > 1 file changed, 4 insertions(+), 2 deletions(-) > > diff --git a/mm/shmem.c b/mm/shmem.c > index 3b5dc21b323c..d76812dd3cef 100644 > --- a/mm/shmem.c > +++ b/mm/shmem.c > @@ -1599,6 +1599,7 @@ int shmem_writeout(struct folio *folio, struct swap_iocb **plug, > struct shmem_sb_info *sbinfo = SHMEM_SB(inode->i_sb); > pgoff_t index; > int nr_pages; > + int ret; > bool split = false; > > if ((info->flags & SHMEM_F_LOCKED) || sbinfo->noswap) > @@ -1679,7 +1680,8 @@ int shmem_writeout(struct folio *folio, struct swap_iocb **plug, > folio_mark_uptodate(folio); > } > > - if (!folio_alloc_swap(folio)) { > + ret = folio_alloc_swap(folio); > + if (!ret) { > bool first_swapped = shmem_recalc_inode(inode, 0, nr_pages); > int error; > > @@ -1732,7 +1734,7 @@ int shmem_writeout(struct folio *folio, struct swap_iocb **plug, > swap_cache_del_folio(folio); > goto redirty; > } > - if (nr_pages > 1) > + if (ret == -E2BIG) I'd rather keep if (nr_pages > 1 && ret == -E2BIG), as it makes the large folio case more explicit and easier to understand. > goto try_split; > redirty: > folio_mark_dirty(folio); Best Regards Barry ^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-07-23 15:16 UTC | newest] Thread overview: 12+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-07-17 12:25 [RFC PATCH v3 0/4] mm: avoid large folio splits when swap is unavailable Xueyuan Chen 2026-07-17 12:25 ` [RFC PATCH v3 1/4] mm: add page_counter_margin() Xueyuan Chen 2026-07-17 12:25 ` [RFC PATCH v3 2/4] mm: distinguish large folio swap allocation failures Xueyuan Chen 2026-07-21 8:15 ` Barry Song 2026-07-21 11:53 ` Johannes Weiner 2026-07-23 14:49 ` Xueyuan Chen 2026-07-21 16:13 ` Youngjun Park 2026-07-23 15:15 ` Xueyuan Chen 2026-07-17 12:25 ` [RFC PATCH v3 3/4] mm/vmscan: avoid pointless large folio splits without swap Xueyuan Chen 2026-07-21 8:00 ` Barry Song 2026-07-17 12:25 ` [RFC PATCH v3 4/4] mm/shmem: split large folios only on -E2BIG Xueyuan Chen 2026-07-21 7:57 ` Barry Song
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox