From: Nhat Pham <nphamcs@gmail.com>
To: linux-mm@kvack.org, linux-kernel@vger.kernel.org,
akpm@linux-foundation.org, axelrasmussen@google.com,
baohua@kernel.org, baolin.wang@linux.alibaba.com,
baoquan.he@linux.dev, brauner@kernel.org, chrisl@kernel.org,
david@kernel.org, hannes@cmpxchg.org, kasong@tencent.com,
ljs@kernel.org, mhocko@kernel.org, nphamcs@gmail.com,
qi.zheng@linux.dev, shakeel.butt@linux.dev,
shikemeng@huaweicloud.com, weixugc@google.com,
willy@infradead.org, youngjun.park@lge.com, yuanchu@google.com
Cc: stable@vger.kernel.org, ryncsn@gmail.com
Subject: [PATCH 2/2] mm/swap: publish cluster tables after reacquiring allocator locks
Date: Fri, 6 Nov 2026 08:04:32 -0800 [thread overview]
Message-ID: <20261106160432.2552507-3-nphamcs@gmail.com> (raw)
In-Reply-To: <20261106160432.2552507-1-nphamcs@gmail.com>
Under memory pressure, swap_cluster_populate() can publish a table
while its locks are dropped for a sleeping allocation. A cached
allocator can consume and free that table before the populating
allocator resumes, leaving the resumed scan with a NULL table.
CPU0 CPU1
swap_cluster_populate()
drop local/global/ci locks
allocate and publish table
swap_alloc_fast()
consume the table
charge failure cleanup
free the table
reacquire local/global/ci
return ci
alloc_swap_scan_cluster()
read the missing table
Keep the count table, cgroup table and zero bitmap private until
swap_cluster_alloc_table() restores all allocator locks. Install them
under the cluster lock and retain it through the scan. Initial bad-slot
setup takes only the cluster lock because the device is unpublished.
A stale reclaimer can return the isolated cluster to allocation while
its locks are dropped. Abandon installation if the list state changes or
a table appears, freeing only private allocations. Restore locks on
allocation failure too, so the caller can return a still-isolated
cluster to its free list.
Reported-by: Sashiko <sashiko-bot@kernel.org>
Link: https://lore.kernel.org/all/sashiko-outbox-181084@kernel.org/
Link: https://lore.kernel.org/all/sashiko-outbox-181086@kernel.org/
Co-developed-by: Kairui Song <ryncsn@gmail.com>
Fixes: cdd77f84d966 ("mm, swap: consolidate cluster allocation helpers")
Cc: stable@vger.kernel.org
Signed-off-by: Nhat Pham <nphamcs@gmail.com>
---
mm/swapfile.c | 106 +++++++++++++++++++++++++++++++++++---------------
1 file changed, 74 insertions(+), 32 deletions(-)
diff --git a/mm/swapfile.c b/mm/swapfile.c
index 189f04d8838f..162a73a9e663 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -449,15 +449,23 @@ static void swap_cluster_free_table(struct swap_cluster_info *ci)
swap_cluster_free_count_table(table);
}
-static int swap_cluster_alloc_table(struct swap_cluster_info *ci, gfp_t gfp)
+/*
+ * Return with ci locked. An unlocked allocator also needs si's outer locks;
+ * initial bad-slot setup passes NULL because the device is not published.
+ */
+static int swap_cluster_alloc_table(struct swap_info_struct *si,
+ struct swap_cluster_info *ci,
+ gfp_t gfp, bool locked)
{
struct swap_table *table = NULL;
+#ifdef CONFIG_MEMCG
+ struct swap_memcg_table *memcg_table = NULL;
+#endif
+#if !SWAP_TABLE_HAS_ZEROFLAG
+ unsigned long *zero_bitmap = NULL;
+#endif
struct folio *folio;
-
- /* The cluster must be empty and not on any list during allocation. */
- VM_WARN_ON_ONCE(ci->flags || !cluster_is_empty(ci));
- if (rcu_access_pointer(ci->table))
- return 0;
+ int ret = -ENOMEM;
if (SWP_TABLE_USE_PAGE) {
folio = folio_alloc(gfp | __GFP_ZERO, 0);
@@ -467,27 +475,49 @@ static int swap_cluster_alloc_table(struct swap_cluster_info *ci, gfp_t gfp)
table = kmem_cache_zalloc(swap_table_cachep, gfp);
}
if (!table)
- return -ENOMEM;
+ goto lock;
#ifdef CONFIG_MEMCG
if (!mem_cgroup_disabled()) {
- VM_WARN_ON_ONCE(ci->memcg_table);
- ci->memcg_table = kzalloc_obj(*ci->memcg_table, gfp);
- if (!ci->memcg_table) {
- swap_cluster_free_count_table(table);
- return -ENOMEM;
- }
+ memcg_table = kzalloc_obj(*ci->memcg_table, gfp);
+ if (!memcg_table)
+ goto lock;
}
#endif
#if !SWAP_TABLE_HAS_ZEROFLAG
- VM_WARN_ON_ONCE(ci->zero_bitmap);
- ci->zero_bitmap = bitmap_zalloc(SWAPFILE_CLUSTER, gfp);
- if (!ci->zero_bitmap) {
- swap_cluster_free_table(ci);
- swap_cluster_free_count_table(table);
- return -ENOMEM;
+ zero_bitmap = bitmap_zalloc(SWAPFILE_CLUSTER, gfp);
+ if (!zero_bitmap)
+ goto lock;
+#endif
+ ret = 0;
+
+lock:
+ if (!locked) {
+ if (si) {
+ local_lock(&percpu_swap_cluster.lock);
+ if (!(si->flags & SWP_SOLIDSTATE))
+ spin_lock(&si->global_cluster_lock);
+ }
+ spin_lock(&ci->lock);
}
+ lockdep_assert_held(&ci->lock);
+
+ /* A stale reclaimer may have returned the cluster to allocation. */
+ if (ci->flags != CLUSTER_FLAG_NONE || cluster_table_is_alloced(ci))
+ ret = -EAGAIN;
+ if (ret)
+ goto free;
+
+ /* The cluster must be empty and not on any list during allocation. */
+ VM_WARN_ON_ONCE(!cluster_is_empty(ci));
+#ifdef CONFIG_MEMCG
+ VM_WARN_ON_ONCE(ci->memcg_table);
+ ci->memcg_table = memcg_table;
+#endif
+#if !SWAP_TABLE_HAS_ZEROFLAG
+ VM_WARN_ON_ONCE(ci->zero_bitmap);
+ ci->zero_bitmap = zero_bitmap;
#endif
/*
@@ -496,6 +526,17 @@ static int swap_cluster_alloc_table(struct swap_cluster_info *ci, gfp_t gfp)
*/
rcu_assign_pointer(ci->table, table);
return 0;
+
+free:
+#ifdef CONFIG_MEMCG
+ kfree(memcg_table);
+#endif
+#if !SWAP_TABLE_HAS_ZEROFLAG
+ bitmap_free(zero_bitmap);
+#endif
+ if (table)
+ swap_cluster_free_count_table(table);
+ return ret;
}
/*
@@ -546,8 +587,8 @@ swap_cluster_populate(struct swap_info_struct *si,
lockdep_assert_held(&si->global_cluster_lock);
lockdep_assert_held(&ci->lock);
- if (!swap_cluster_alloc_table(ci, __GFP_HIGH | __GFP_NOMEMALLOC |
- __GFP_NOWARN))
+ if (!swap_cluster_alloc_table(si, ci, __GFP_HIGH | __GFP_NOMEMALLOC |
+ __GFP_NOWARN, true))
return ci;
/*
@@ -560,8 +601,8 @@ swap_cluster_populate(struct swap_info_struct *si,
spin_unlock(&si->global_cluster_lock);
local_unlock(&percpu_swap_cluster.lock);
- ret = swap_cluster_alloc_table(ci, __GFP_HIGH | __GFP_NOMEMALLOC |
- GFP_KERNEL);
+ ret = swap_cluster_alloc_table(si, ci, __GFP_HIGH | __GFP_NOMEMALLOC |
+ GFP_KERNEL, false);
/*
* Back to atomic context. We might have migrated to a new CPU with a
@@ -571,13 +612,9 @@ swap_cluster_populate(struct swap_info_struct *si,
* could happen with ignoring the percpu cluster is fragmentation,
* which is acceptable since this fallback and race is rare.
*/
- local_lock(&percpu_swap_cluster.lock);
- if (!(si->flags & SWP_SOLIDSTATE))
- spin_lock(&si->global_cluster_lock);
- spin_lock(&ci->lock);
-
if (ret) {
- move_cluster(si, ci, &si->free_clusters, CLUSTER_FLAG_FREE);
+ if (ret != -EAGAIN)
+ move_cluster(si, ci, &si->free_clusters, CLUSTER_FLAG_FREE);
spin_unlock(&ci->lock);
return NULL;
}
@@ -825,9 +862,13 @@ static int swap_cluster_setup_bad_slot(struct swap_info_struct *si,
ci = cluster_info + idx;
/* Need to allocate swap table first for initial bad slot marking. */
- if (!ci->count && swap_cluster_alloc_table(ci, GFP_KERNEL))
- return -ENOMEM;
- spin_lock(&ci->lock);
+ if (!ci->count) {
+ ret = swap_cluster_alloc_table(NULL, ci, GFP_KERNEL, false);
+ if (ret)
+ goto out;
+ } else {
+ spin_lock(&ci->lock);
+ }
/* Check for duplicated bad swap slots. */
if (__swap_table_xchg(ci, ci_off, SWP_TB_BAD) != SWP_TB_NULL) {
pr_warn("Duplicated bad slot offset %d\n", offset);
@@ -835,6 +876,7 @@ static int swap_cluster_setup_bad_slot(struct swap_info_struct *si,
} else {
ci->count++;
}
+out:
spin_unlock(&ci->lock);
WARN_ON(ci->count > SWAPFILE_CLUSTER);
next prev parent reply other threads:[~2026-11-06 16:04 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-06 16:04 [PATCH 0/2] mm: fix swapcache writeout and cluster table publication Nhat Pham
2026-11-06 16:04 ` [PATCH 1/2] mm: vmscan: allow writeout of swapcache folios without anon mappings Nhat Pham
2026-11-10 6:01 ` Baolin Wang
2026-11-06 16:04 ` Nhat Pham [this message]
2026-11-06 19:59 ` [PATCH 2/2] mm/swap: publish cluster tables after reacquiring allocator locks Nhat Pham
2026-11-07 18:02 ` Kairui Song
2026-11-09 16:54 ` Nhat Pham
2026-11-07 16:04 ` sashiko-bot
2026-11-09 16:53 ` Nhat Pham
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=20261106160432.2552507-3-nphamcs@gmail.com \
--to=nphamcs@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=brauner@kernel.org \
--cc=chrisl@kernel.org \
--cc=david@kernel.org \
--cc=hannes@cmpxchg.org \
--cc=kasong@tencent.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=ljs@kernel.org \
--cc=mhocko@kernel.org \
--cc=qi.zheng@linux.dev \
--cc=ryncsn@gmail.com \
--cc=shakeel.butt@linux.dev \
--cc=shikemeng@huaweicloud.com \
--cc=stable@vger.kernel.org \
--cc=weixugc@google.com \
--cc=willy@infradead.org \
--cc=youngjun.park@lge.com \
--cc=yuanchu@google.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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.