Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH v5 0/4] mm: avoid large folio splits when swap is unavailable
@ 2026-07-30 12:23 Xueyuan Chen
  2026-07-30 12:23 ` [RFC PATCH v5 1/4] mm: add page_counter_margin() Xueyuan Chen
                   ` (3 more replies)
  0 siblings, 4 replies; 10+ messages in thread
From: Xueyuan Chen @ 2026-07-30 12:23 UTC (permalink / raw)
  To: akpm, linux-mm
  Cc: linux-kernel, cgroups, zhaonanzhe, baohua, hannes, youngjun.park,
	baolin.wang, hughd, chrisl, kasong, shikemeng, nphamcs,
	baoquan.he, mhocko, roman.gushchin, shakeel.butt, muchun.song,
	david, ljs, liam, vbabka, rppt, surenb, qi.zheng, axelrasmussen,
	yuanchu, weixugc

This is RFC v5 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 function sees both the swap allocation result and the
memcg swap charge result, so callers only split when 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 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 v4 -> RFC v5:
- Fix Patch #3 to jump to activate_locked, not activate_locked_split, when
  folio_alloc_swap() fails with an error other than -E2BIG. The folio has
  not been split in that case, so activate_locked_split would incorrectly
  adjust nr_scanned and nr_pages as if tail pages had been split out.
- https://lore.kernel.org/r/20260730021630.2235914-1-xueyuan.chen21@gmail.com

RFC v3 -> RFC v4:
- Keep global swap availability and memcg swap margin separate, following
  feedback from Barry Song, Johannes Weiner, and Youngjun Park.
- Handle early rejections and memcg charge failures with the refined
  folio_alloc_swap() return-value contract.
- Apply the requested vmscan and shmem condition layout changes.
- https://lore.kernel.org/r/20260717122514.51514-1-xueyuan.chen21@gmail.com

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.
- 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         | 16 ++++++++++----
 mm/memcontrol.c              | 41 ++++++++++++++++++++++++++++++------
 mm/page_counter.c            | 20 ++++++++++++++++++
 mm/shmem.c                   |  7 +++---
 mm/swapfile.c                | 32 +++++++++++++++++++++-------
 mm/vmscan.c                  |  7 +++++-
 7 files changed, 101 insertions(+), 23 deletions(-)


base-commit: c73b725a57f276a3702ca213bde78fca029bc619
-- 
2.47.3


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [RFC PATCH v5 1/4] mm: add page_counter_margin()
  2026-07-30 12:23 [RFC PATCH v5 0/4] mm: avoid large folio splits when swap is unavailable Xueyuan Chen
@ 2026-07-30 12:23 ` Xueyuan Chen
  2026-08-06 13:51   ` David Hildenbrand (Arm)
  2026-07-30 12:23 ` [RFC PATCH v5 2/4] mm: distinguish large folio swap allocation failures Xueyuan Chen
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 10+ messages in thread
From: Xueyuan Chen @ 2026-07-30 12:23 UTC (permalink / raw)
  To: akpm, linux-mm
  Cc: linux-kernel, cgroups, zhaonanzhe, baohua, hannes, youngjun.park,
	baolin.wang, hughd, chrisl, kasong, shikemeng, nphamcs,
	baoquan.he, mhocko, roman.gushchin, shakeel.butt, muchun.song,
	david, ljs, liam, vbabka, rppt, surenb, qi.zheng, axelrasmussen,
	yuanchu, weixugc

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            | 20 ++++++++++++++++++++
 3 files changed, 24 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 8319ad8c5c23..109c08be91cf 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -5670,12 +5670,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..450543f4b318 100644
--- a/mm/page_counter.c
+++ b/mm/page_counter.c
@@ -46,6 +46,26 @@ static void propagate_protected_usage(struct page_counter *c,
 	}
 }
 
+/**
+ * page_counter_margin - remaining usable space within hierarchical limits
+ * @counter: counter
+ *
+ * Return: The minimum value of max minus usage across @counter and all of
+ * its ancestors. The value may be negative during a concurrent charge.
+ */
+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] 10+ messages in thread

* [RFC PATCH v5 2/4] mm: distinguish large folio swap allocation failures
  2026-07-30 12:23 [RFC PATCH v5 0/4] mm: avoid large folio splits when swap is unavailable Xueyuan Chen
  2026-07-30 12:23 ` [RFC PATCH v5 1/4] mm: add page_counter_margin() Xueyuan Chen
@ 2026-07-30 12:23 ` Xueyuan Chen
  2026-08-06 13:56   ` David Hildenbrand (Arm)
  2026-07-30 12:23 ` [RFC PATCH v5 3/4] mm/vmscan: avoid pointless large folio splits without swap Xueyuan Chen
  2026-07-30 12:23 ` [RFC PATCH v5 4/4] mm/shmem: split large folios only on -E2BIG Xueyuan Chen
  3 siblings, 1 reply; 10+ messages in thread
From: Xueyuan Chen @ 2026-07-30 12:23 UTC (permalink / raw)
  To: akpm, linux-mm
  Cc: linux-kernel, cgroups, zhaonanzhe, baohua, hannes, youngjun.park,
	baolin.wang, hughd, chrisl, kasong, shikemeng, nphamcs,
	baoquan.he, mhocko, roman.gushchin, shakeel.butt, muchun.song,
	david, ljs, liam, vbabka, rppt, surenb, qi.zheng, axelrasmussen,
	yuanchu, weixugc

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.

Keep the global free swap count and the remaining hierarchical memcg swap
margin as separate inputs. The memcg charge path reports only its own
margin; folio_alloc_swap() combines the two layers when classifying an
allocation failure.

Return -E2BIG for large folios when a smaller allocation might still fit,
-ENOSPC when no global swap space is available, and -ENOMEM when the
failure is not helped by splitting.

For early large-folio rejections, check global and memcg swap availability
instead of returning -E2BIG unconditionally. On a memcg charge failure,
swap slot allocation has already succeeded, so use the remaining memcg
margin to decide whether a smaller charge might fit.

This only refines folio_alloc_swap() return codes. The reclaim callers are
updated separately.

Suggested-by: Barry Song <baohua@kernel.org>
Suggested-by: Youngjun Park <youngjun.park@lge.com>
Signed-off-by: Xueyuan Chen <xueyuan.chen21@gmail.com>
---
 include/linux/swap.h | 16 ++++++++++++----
 mm/memcontrol.c      | 32 +++++++++++++++++++++++++++++++-
 mm/swapfile.c        | 32 ++++++++++++++++++++++++--------
 3 files changed, 67 insertions(+), 13 deletions(-)

diff --git a/include/linux/swap.h b/include/linux/swap.h
index 0544b2ec4c56..7d12058174ae 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -509,12 +509,13 @@ 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);
-static inline int mem_cgroup_try_charge_swap(struct folio *folio)
+int __mem_cgroup_try_charge_swap(struct folio *folio, long *swap_margin);
+static inline int mem_cgroup_try_charge_swap(struct folio *folio,
+					     long *swap_margin)
 {
 	if (mem_cgroup_disabled())
 		return 0;
-	return __mem_cgroup_try_charge_swap(folio);
+	return __mem_cgroup_try_charge_swap(folio, swap_margin);
 }
 
 extern void __mem_cgroup_uncharge_swap(unsigned short id, unsigned int nr_pages);
@@ -525,10 +526,12 @@ static inline void mem_cgroup_uncharge_swap(unsigned short id, unsigned int nr_p
 	__mem_cgroup_uncharge_swap(id, 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)
+static inline int mem_cgroup_try_charge_swap(struct folio *folio,
+					     long *swap_margin)
 {
 	return 0;
 }
@@ -538,6 +541,11 @@ static inline void mem_cgroup_uncharge_swap(unsigned short id,
 {
 }
 
+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 109c08be91cf..fb0ec439ba2d 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -5592,12 +5592,13 @@ int __init mem_cgroup_init(void)
 /**
  * __mem_cgroup_try_charge_swap - try charging swap space for a folio
  * @folio: folio being added to swap
+ * @swap_margin: remaining memcg swap margin if allocation or charge fails
  *
  * Try to charge @folio's memcg for the swap space at folio->swap.
  *
  * Returns 0 on success, -ENOMEM on failure.
  */
-int __mem_cgroup_try_charge_swap(struct folio *folio)
+int __mem_cgroup_try_charge_swap(struct folio *folio, long *swap_margin)
 {
 	unsigned int nr_pages = folio_nr_pages(folio);
 	struct swap_cluster_info *ci;
@@ -5616,6 +5617,7 @@ int __mem_cgroup_try_charge_swap(struct folio *folio)
 	rcu_read_lock();
 	memcg = obj_cgroup_memcg(objcg);
 	if (!folio_test_swapcache(folio)) {
+		*swap_margin = page_counter_margin(&memcg->swap);
 		memcg_memory_event(memcg, MEMCG_SWAP_FAIL);
 		rcu_read_unlock();
 		return 0;
@@ -5629,6 +5631,7 @@ int __mem_cgroup_try_charge_swap(struct folio *folio)
 	    !page_counter_try_charge(&memcg->swap, nr_pages, &counter)) {
 		memcg_memory_event(memcg, MEMCG_SWAP_MAX);
 		memcg_memory_event(memcg, MEMCG_SWAP_FAIL);
+		*swap_margin = page_counter_margin(counter);
 		mem_cgroup_private_id_put(memcg, nr_pages);
 		return -ENOMEM;
 	}
@@ -5676,6 +5679,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 70b90fa9c2a0..ae62c9f9c0f2 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -1735,23 +1735,28 @@ 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,
+ * %-ENOSPC if no global swap space is available, or %-ENOMEM 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. Check below
+		 * whether splitting and retrying can make progress.
 		 */
-		if (!IS_ENABLED(CONFIG_THP_SWAP))
-			return -EAGAIN;
+		if (!IS_ENABLED(CONFIG_THP_SWAP)) {
+			swap_margin = mem_cgroup_get_folio_swap_margin(folio);
+			goto failed;
+		}
 
 		/*
 		 * Allocation size should never exceed cluster size
@@ -1759,7 +1764,8 @@ int folio_alloc_swap(struct folio *folio)
 		 */
 		if (size > SWAPFILE_CLUSTER) {
 			VM_WARN_ON_ONCE(1);
-			return -EINVAL;
+			swap_margin = mem_cgroup_get_folio_swap_margin(folio);
+			goto failed;
 		}
 	}
 
@@ -1775,13 +1781,23 @@ 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_margin))) {
 		swap_cache_del_folio(folio);
+		return order && swap_margin > 0 ? -E2BIG : -ENOMEM;
+	}
 
 	if (unlikely(!folio_test_swapcache(folio)))
-		return -ENOMEM;
+		goto failed;
 
 	return 0;
+
+failed:
+	if (get_nr_swap_pages() <= 0)
+		return -ENOSPC;
+	if (swap_margin <= 0)
+		return -ENOMEM;
+
+	return order ? -E2BIG : -ENOMEM;
 }
 
 /**
-- 
2.47.3



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [RFC PATCH v5 3/4] mm/vmscan: avoid pointless large folio splits without swap
  2026-07-30 12:23 [RFC PATCH v5 0/4] mm: avoid large folio splits when swap is unavailable Xueyuan Chen
  2026-07-30 12:23 ` [RFC PATCH v5 1/4] mm: add page_counter_margin() Xueyuan Chen
  2026-07-30 12:23 ` [RFC PATCH v5 2/4] mm: distinguish large folio swap allocation failures Xueyuan Chen
@ 2026-07-30 12:23 ` Xueyuan Chen
  2026-08-06 13:58   ` David Hildenbrand (Arm)
  2026-08-07  2:03   ` Baolin Wang
  2026-07-30 12:23 ` [RFC PATCH v5 4/4] mm/shmem: split large folios only on -E2BIG Xueyuan Chen
  3 siblings, 2 replies; 10+ messages in thread
From: Xueyuan Chen @ 2026-07-30 12:23 UTC (permalink / raw)
  To: akpm, linux-mm
  Cc: linux-kernel, cgroups, zhaonanzhe, baohua, hannes, youngjun.park,
	baolin.wang, hughd, chrisl, kasong, shikemeng, nphamcs,
	baoquan.he, mhocko, roman.gushchin, shakeel.butt, muchun.song,
	david, ljs, liam, vbabka, rppt, surenb, qi.zheng, axelrasmussen,
	yuanchu, weixugc

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>
Signed-off-by: Xueyuan Chen <xueyuan.chen21@gmail.com>
---
 mm/vmscan.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/mm/vmscan.c b/mm/vmscan.c
index 26df81e773ff..457049e130df 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -1263,6 +1263,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))
@@ -1281,11 +1283,14 @@ 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))
 					goto activate_locked_split;
+				if (ret != -E2BIG)
+					goto activate_locked;
 				/* Fallback to swap normal pages */
 				if (split_folio_to_list(folio, folio_list))
 					goto activate_locked;
-- 
2.47.3



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [RFC PATCH v5 4/4] mm/shmem: split large folios only on -E2BIG
  2026-07-30 12:23 [RFC PATCH v5 0/4] mm: avoid large folio splits when swap is unavailable Xueyuan Chen
                   ` (2 preceding siblings ...)
  2026-07-30 12:23 ` [RFC PATCH v5 3/4] mm/vmscan: avoid pointless large folio splits without swap Xueyuan Chen
@ 2026-07-30 12:23 ` Xueyuan Chen
  2026-08-06 13:59   ` David Hildenbrand (Arm)
  3 siblings, 1 reply; 10+ messages in thread
From: Xueyuan Chen @ 2026-07-30 12:23 UTC (permalink / raw)
  To: akpm, linux-mm
  Cc: linux-kernel, cgroups, zhaonanzhe, baohua, hannes, youngjun.park,
	baolin.wang, hughd, chrisl, kasong, shikemeng, nphamcs,
	baoquan.he, mhocko, roman.gushchin, shakeel.butt, muchun.song,
	david, ljs, liam, vbabka, rppt, surenb, qi.zheng, axelrasmussen,
	yuanchu, weixugc

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>
Reviewed-by: Baolin Wang <baolin.wang@linux.alibaba.com>
Signed-off-by: Xueyuan Chen <xueyuan.chen21@gmail.com>
---
 mm/shmem.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/mm/shmem.c b/mm/shmem.c
index 6641823bed16..eff38bb33d8a 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -1608,7 +1608,7 @@ int shmem_writeout(struct swap_io_ctx *ctx, struct folio *folio,
 	struct shmem_inode_info *info = SHMEM_I(inode);
 	struct shmem_sb_info *sbinfo = SHMEM_SB(inode->i_sb);
 	pgoff_t index;
-	int nr_pages;
+	int nr_pages, ret;
 	bool split = false;
 
 	if ((info->flags & SHMEM_F_LOCKED) || sbinfo->noswap)
@@ -1689,7 +1689,8 @@ int shmem_writeout(struct swap_io_ctx *ctx, struct folio *folio,
 		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;
 
@@ -1742,7 +1743,7 @@ int shmem_writeout(struct swap_io_ctx *ctx, struct folio *folio,
 		swap_cache_del_folio(folio);
 		goto redirty;
 	}
-	if (nr_pages > 1)
+	if (nr_pages > 1 && ret == -E2BIG)
 		goto try_split;
 redirty:
 	folio_mark_dirty(folio);
-- 
2.47.3



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [RFC PATCH v5 1/4] mm: add page_counter_margin()
  2026-07-30 12:23 ` [RFC PATCH v5 1/4] mm: add page_counter_margin() Xueyuan Chen
@ 2026-08-06 13:51   ` David Hildenbrand (Arm)
  0 siblings, 0 replies; 10+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-06 13:51 UTC (permalink / raw)
  To: Xueyuan Chen, akpm, linux-mm
  Cc: linux-kernel, cgroups, zhaonanzhe, baohua, hannes, youngjun.park,
	baolin.wang, hughd, chrisl, kasong, shikemeng, nphamcs,
	baoquan.he, mhocko, roman.gushchin, shakeel.butt, muchun.song,
	ljs, liam, vbabka, rppt, surenb, qi.zheng, axelrasmussen, yuanchu,
	weixugc

On 7/30/26 14:23, Xueyuan Chen wrote:
> 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>
> ---

Reviewed-by: David Hildenbrand (Arm) <david@kernel.org>

-- 
Cheers,

David


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [RFC PATCH v5 2/4] mm: distinguish large folio swap allocation failures
  2026-07-30 12:23 ` [RFC PATCH v5 2/4] mm: distinguish large folio swap allocation failures Xueyuan Chen
@ 2026-08-06 13:56   ` David Hildenbrand (Arm)
  0 siblings, 0 replies; 10+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-06 13:56 UTC (permalink / raw)
  To: Xueyuan Chen, akpm, linux-mm
  Cc: linux-kernel, cgroups, zhaonanzhe, baohua, hannes, youngjun.park,
	baolin.wang, hughd, chrisl, kasong, shikemeng, nphamcs,
	baoquan.he, mhocko, roman.gushchin, shakeel.butt, muchun.song,
	ljs, liam, vbabka, rppt, surenb, qi.zheng, axelrasmussen, yuanchu,
	weixugc

On 7/30/26 14:23, Xueyuan Chen 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.
> 
> Keep the global free swap count and the remaining hierarchical memcg swap
> margin as separate inputs. The memcg charge path reports only its own
> margin; folio_alloc_swap() combines the two layers when classifying an
> allocation failure.
> 
> Return -E2BIG for large folios when a smaller allocation might still fit,
> -ENOSPC when no global swap space is available, and -ENOMEM when the
> failure is not helped by splitting.
> 
> For early large-folio rejections, check global and memcg swap availability
> instead of returning -E2BIG unconditionally. On a memcg charge failure,
> swap slot allocation has already succeeded, so use the remaining memcg
> margin to decide whether a smaller charge might fit.
> 
> This only refines folio_alloc_swap() return codes. The reclaim callers are
> updated separately.
> 
> Suggested-by: Barry Song <baohua@kernel.org>
> Suggested-by: Youngjun Park <youngjun.park@lge.com>
> Signed-off-by: Xueyuan Chen <xueyuan.chen21@gmail.com>
> ---
>  include/linux/swap.h | 16 ++++++++++++----
>  mm/memcontrol.c      | 32 +++++++++++++++++++++++++++++++-
>  mm/swapfile.c        | 32 ++++++++++++++++++++++++--------
>  3 files changed, 67 insertions(+), 13 deletions(-)
> 
> diff --git a/include/linux/swap.h b/include/linux/swap.h
> index 0544b2ec4c56..7d12058174ae 100644
> --- a/include/linux/swap.h
> +++ b/include/linux/swap.h
> @@ -509,12 +509,13 @@ 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);
> -static inline int mem_cgroup_try_charge_swap(struct folio *folio)
> +int __mem_cgroup_try_charge_swap(struct folio *folio, long *swap_margin);
> +static inline int mem_cgroup_try_charge_swap(struct folio *folio,
> +					     long *swap_margin)

Nit: two tab indent

[...]

> -static inline int mem_cgroup_try_charge_swap(struct folio *folio)
> +static inline int mem_cgroup_try_charge_swap(struct folio *folio,
> +					     long *swap_margin)


Nothing else jumped at me

Acked-by: David Hildenbrand (Arm) <david@kernel.org>

-- 
Cheers,

David


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [RFC PATCH v5 3/4] mm/vmscan: avoid pointless large folio splits without swap
  2026-07-30 12:23 ` [RFC PATCH v5 3/4] mm/vmscan: avoid pointless large folio splits without swap Xueyuan Chen
@ 2026-08-06 13:58   ` David Hildenbrand (Arm)
  2026-08-07  2:03   ` Baolin Wang
  1 sibling, 0 replies; 10+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-06 13:58 UTC (permalink / raw)
  To: Xueyuan Chen, akpm, linux-mm
  Cc: linux-kernel, cgroups, zhaonanzhe, baohua, hannes, youngjun.park,
	baolin.wang, hughd, chrisl, kasong, shikemeng, nphamcs,
	baoquan.he, mhocko, roman.gushchin, shakeel.butt, muchun.song,
	ljs, liam, vbabka, rppt, surenb, qi.zheng, axelrasmussen, yuanchu,
	weixugc

On 7/30/26 14:23, Xueyuan Chen 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>

Do you have a link to the report?

I assume this is not Fixes-worthy.

> Signed-off-by: Barry Song (Xiaomi) <baohua@kernel.org>
> Signed-off-by: Xueyuan Chen <xueyuan.chen21@gmail.com>
> ---
>  mm/vmscan.c | 7 ++++++-
>  1 file changed, 6 insertions(+), 1 deletion(-)
> 
> diff --git a/mm/vmscan.c b/mm/vmscan.c
> index 26df81e773ff..457049e130df 100644
> --- a/mm/vmscan.c
> +++ b/mm/vmscan.c
> @@ -1263,6 +1263,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))
> @@ -1281,11 +1283,14 @@ 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))
>  					goto activate_locked_split;
> +				if (ret != -E2BIG)
> +					goto activate_locked;
>  				/* Fallback to swap normal pages */
>  				if (split_folio_to_list(folio, folio_list))
>  					goto activate_locked;

Acked-by: David Hildenbrand (Arm) <david@kernel.org>

-- 
Cheers,

David


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [RFC PATCH v5 4/4] mm/shmem: split large folios only on -E2BIG
  2026-07-30 12:23 ` [RFC PATCH v5 4/4] mm/shmem: split large folios only on -E2BIG Xueyuan Chen
@ 2026-08-06 13:59   ` David Hildenbrand (Arm)
  0 siblings, 0 replies; 10+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-06 13:59 UTC (permalink / raw)
  To: Xueyuan Chen, akpm, linux-mm
  Cc: linux-kernel, cgroups, zhaonanzhe, baohua, hannes, youngjun.park,
	baolin.wang, hughd, chrisl, kasong, shikemeng, nphamcs,
	baoquan.he, mhocko, roman.gushchin, shakeel.butt, muchun.song,
	ljs, liam, vbabka, rppt, surenb, qi.zheng, axelrasmussen, yuanchu,
	weixugc

On 7/30/26 14:23, Xueyuan Chen 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>
> Reviewed-by: Baolin Wang <baolin.wang@linux.alibaba.com>
> Signed-off-by: Xueyuan Chen <xueyuan.chen21@gmail.com>
> ---

Acked-by: David Hildenbrand (Arm) <david@kernel.org>

-- 
Cheers,

David


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [RFC PATCH v5 3/4] mm/vmscan: avoid pointless large folio splits without swap
  2026-07-30 12:23 ` [RFC PATCH v5 3/4] mm/vmscan: avoid pointless large folio splits without swap Xueyuan Chen
  2026-08-06 13:58   ` David Hildenbrand (Arm)
@ 2026-08-07  2:03   ` Baolin Wang
  1 sibling, 0 replies; 10+ messages in thread
From: Baolin Wang @ 2026-08-07  2:03 UTC (permalink / raw)
  To: Xueyuan Chen, akpm, linux-mm
  Cc: linux-kernel, cgroups, zhaonanzhe, baohua, hannes, youngjun.park,
	hughd, chrisl, kasong, shikemeng, nphamcs, baoquan.he, mhocko,
	roman.gushchin, shakeel.butt, muchun.song, david, ljs, liam,
	vbabka, rppt, surenb, qi.zheng, axelrasmussen, yuanchu, weixugc



On 7/30/26 8:23 PM, Xueyuan Chen 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>
> Signed-off-by: Xueyuan Chen <xueyuan.chen21@gmail.com>
> ---

LGTM. Feel free to add:
Reviewed-by: Baolin Wang <baolin.wang@linux.alibaba.com>


^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2026-08-07  2:04 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-30 12:23 [RFC PATCH v5 0/4] mm: avoid large folio splits when swap is unavailable Xueyuan Chen
2026-07-30 12:23 ` [RFC PATCH v5 1/4] mm: add page_counter_margin() Xueyuan Chen
2026-08-06 13:51   ` David Hildenbrand (Arm)
2026-07-30 12:23 ` [RFC PATCH v5 2/4] mm: distinguish large folio swap allocation failures Xueyuan Chen
2026-08-06 13:56   ` David Hildenbrand (Arm)
2026-07-30 12:23 ` [RFC PATCH v5 3/4] mm/vmscan: avoid pointless large folio splits without swap Xueyuan Chen
2026-08-06 13:58   ` David Hildenbrand (Arm)
2026-08-07  2:03   ` Baolin Wang
2026-07-30 12:23 ` [RFC PATCH v5 4/4] mm/shmem: split large folios only on -E2BIG Xueyuan Chen
2026-08-06 13:59   ` David Hildenbrand (Arm)

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox