From: Xueyuan Chen <xueyuan.chen21@gmail.com>
To: akpm@linux-foundation.org, linux-mm@kvack.org
Cc: linux-kernel@vger.kernel.org, cgroups@vger.kernel.org,
zhaonanzhe@xiaomi.com, baohua@kernel.org, hannes@cmpxchg.org,
youngjun.park@lge.com, baolin.wang@linux.alibaba.com,
hughd@google.com, chrisl@kernel.org, kasong@tencent.com,
shikemeng@huaweicloud.com, nphamcs@gmail.com,
baoquan.he@linux.dev, mhocko@kernel.org,
roman.gushchin@linux.dev, shakeel.butt@linux.dev,
muchun.song@linux.dev, david@kernel.org, ljs@kernel.org,
liam@infradead.org, vbabka@kernel.org, rppt@kernel.org,
surenb@google.com, qi.zheng@linux.dev, axelrasmussen@google.com,
yuanchu@google.com, weixugc@google.com
Subject: [RFC PATCH v5 2/4] mm: distinguish large folio swap allocation failures
Date: Thu, 30 Jul 2026 20:23:02 +0800 [thread overview]
Message-ID: <20260730122304.2496440-3-xueyuan.chen21@gmail.com> (raw)
In-Reply-To: <20260730122304.2496440-1-xueyuan.chen21@gmail.com>
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
next prev parent reply other threads:[~2026-07-30 12:23 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
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 ` Xueyuan Chen [this message]
2026-08-06 13:56 ` [RFC PATCH v5 2/4] mm: distinguish large folio swap allocation failures David Hildenbrand (Arm)
2026-08-07 8:29 ` Kairui Song
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)
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260730122304.2496440-3-xueyuan.chen21@gmail.com \
--to=xueyuan.chen21@gmail.com \
--cc=akpm@linux-foundation.org \
--cc=axelrasmussen@google.com \
--cc=baohua@kernel.org \
--cc=baolin.wang@linux.alibaba.com \
--cc=baoquan.he@linux.dev \
--cc=cgroups@vger.kernel.org \
--cc=chrisl@kernel.org \
--cc=david@kernel.org \
--cc=hannes@cmpxchg.org \
--cc=hughd@google.com \
--cc=kasong@tencent.com \
--cc=liam@infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=ljs@kernel.org \
--cc=mhocko@kernel.org \
--cc=muchun.song@linux.dev \
--cc=nphamcs@gmail.com \
--cc=qi.zheng@linux.dev \
--cc=roman.gushchin@linux.dev \
--cc=rppt@kernel.org \
--cc=shakeel.butt@linux.dev \
--cc=shikemeng@huaweicloud.com \
--cc=surenb@google.com \
--cc=vbabka@kernel.org \
--cc=weixugc@google.com \
--cc=youngjun.park@lge.com \
--cc=yuanchu@google.com \
--cc=zhaonanzhe@xiaomi.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox