From: sashiko-bot@kernel.org
To: "Nhat Pham" <nphamcs@gmail.com>, akpm@linux-foundation.org
Cc: chrisl@kernel.org, kasong@tencent.com, hannes@cmpxchg.org,
mhocko@kernel.org, roman.gushchin@linux.dev,
shakeel.butt@linux.dev, yosry@kernel.org, david@kernel.org,
muchun.song@linux.dev, shikemeng@huaweicloud.com,
baoquan.he@linux.dev, baohua@kernel.org, youngjun.park@lge.com,
chengming.zhou@linux.dev, 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, riel@surriel.com, gourry@gourry.net,
haowenchao22@gmail.com, corbet@lwn.net, hughd@google.com,
baolin.wang@linux.alibaba.com, tj@kernel.org, mkoutny@suse.com,
skhan@linuxfoundation.org, rdunlap@infradead.org,
kunwu.chan@linux.dev, kernel-team@meta.com, linux-mm@kvack.org
Subject: Re: [RFC PATCH v6 12/12] mm, swap: back vswap clusters with a VM_SPARSE array
Date: Thu, 22 Oct 2026 06:19:27 +0000 [thread overview]
Message-ID: <sashiko-outbox-181086@kernel.org> (raw)
In-Reply-To: <20261021061837.1569125-3-nphamcs@gmail.com>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] mm: vswap_grow_clusters() publishes nr_mapped_clusters before spin_lock_init()
- [High] mm, swap: race on unlocked table publish in swap_cluster_populate()
--
commit f6800a3010801f9cd20edba92bfbe97989790bd2
Author: Nhat Pham <nphamcs@gmail.com>
mm, swap: back vswap clusters with a VM_SPARSE array
This commit replaces the xarray of dynamically allocated vswap cluster
structures with a VM_SPARSE vmalloc array that grows on demand a page at a
time. Emptied vswap clusters now free their tables and return to
si->free_clusters for reuse rather than being freed via RCU.
Link: https://lore.kernel.org/all/20260916101929.149106-1-hebaoquan@kylinos.cn/
> diff --git a/mm/swapfile.c b/mm/swapfile.c
> index fe5460d15f555..4cb6586327c45 100644
> --- a/mm/swapfile.c
> +++ b/mm/swapfile.c
[ ... ]
> @@ -519,12 +550,19 @@ static int swap_cluster_alloc_table(struct swap_info_struct *si,
> 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_table(si, ci);
> swap_cluster_free_count_table(table);
> return -ENOMEM;
> }
> #endif
>
> + /* The virtual table shares the swap table's lifetime. */
> + if (swap_is_vswap(si) && vswap_cluster_alloc_vtable(ci, gfp)) {
> + swap_cluster_free_table(si, ci);
> + swap_cluster_free_count_table(table);
> + return -ENOMEM;
> + }
> +
> /*
> * Make tables visible to cluster_is_usable() after everything is
> * ready.
> @@ -575,10 +613,8 @@ swap_cluster_populate(struct swap_info_struct *si,
> /*
> * Only cluster isolation from the allocator does table allocation.
> * Swap allocator uses percpu clusters and holds the local lock.
> - * vswap clusters are destroyed rather than freed to si->free_clusters.
> */
> - VM_WARN_ON_ONCE(swap_is_vswap(si));
> - lockdep_assert_held(&this_cpu_ptr(&percpu_swap_cluster)->lock);
> + swap_percpu_cluster_assert_held(si);
> if (!(si->flags & SWP_SOLIDSTATE))
> lockdep_assert_held(&si->global_cluster_lock);
> lockdep_assert_held(&ci->lock);
> @@ -595,7 +631,7 @@ swap_cluster_populate(struct swap_info_struct *si,
> spin_unlock(&ci->lock);
> if (!(si->flags & SWP_SOLIDSTATE))
> spin_unlock(&si->global_cluster_lock);
> - local_unlock(&percpu_swap_cluster.lock);
> + swap_percpu_cluster_unlock(si);
>
> ret = swap_cluster_alloc_table(si, ci, __GFP_HIGH | __GFP_NOMEMALLOC |
> GFP_KERNEL);
> @@ -608,7 +644,7 @@ 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);
> + swap_percpu_cluster_lock(si);
> if (!(si->flags & SWP_SOLIDSTATE))
> spin_lock(&si->global_cluster_lock);
> spin_lock(&ci->lock);
[Severity: High]
Can a concurrent fast-path allocator race with the unlocked
swap_cluster_alloc_table() call in swap_cluster_populate()?
With vswap clusters now returning to si->free_clusters in __free_cluster()
instead of being destroyed with CLUSTER_FLAG_DEAD, other CPUs (or other
orders on the same CPU) can still hold an offset into a freed cluster in
percpu_vswap_cluster.offset[order].
When cluster_alloc_swap_entry() -> alloc_swap_scan_list() ->
isolate_lock_cluster() isolates ci from si->free_clusters, it sets
ci->flags = CLUSTER_FLAG_NONE and calls swap_cluster_populate(). If the
initial atomic allocation fails, swap_cluster_populate() drops ci->lock and
calls swap_cluster_alloc_table(si, ci, __GFP_HIGH | __GFP_NOMEMALLOC |
GFP_KERNEL), which publishes ci->virtual_table and ci->table before ci->lock
is re-acquired:
mm/swapfile.c:swap_cluster_alloc_table() {
...
rcu_assign_pointer(ci->table, table);
return 0;
}
Because CLUSTER_FLAG_NONE is <= CLUSTER_FLAG_USABLE, an isolated cluster is
only prevented from passing cluster_is_usable(ci, order) by ci->table being
NULL. Once rcu_assign_pointer(ci->table, table) executes while ci->lock is
dropped, a concurrent vswap_alloc() (or swap_alloc_fast()) with a cached
offset into ci can lock ci->lock, see cluster_is_usable(ci, order) return
true, and allocate entries via alloc_swap_scan_cluster():
mm/swapfile.c:vswap_alloc() {
if (offset != SWAP_ENTRY_INVALID) {
ci = swap_cluster_lock(vswap_si, offset);
if (cluster_is_usable(ci, order)) {
if (cluster_is_empty(ci))
offset = cluster_offset(vswap_si, ci);
alloc_swap_scan_cluster(vswap_si, ci, folio, offset,
NULL);
} ...
}
}
If that fast-path allocation claims ci for a different order and relocates it
onto another cluster list, or if those entries are then freed so
__free_cluster() frees ci->table and ci->virtual_table back to NULL before
swap_cluster_populate() re-acquires spin_lock(&ci->lock),
swap_cluster_populate() will still see ret == 0 and return ci to
alloc_swap_scan_list() -> alloc_swap_scan_cluster().
Could this trigger VM_WARN_ON(!cluster_is_usable(ci, order)) in
alloc_swap_scan_cluster(), corrupt cluster list and order state, or
dereference a NULL ci->table in cluster_scan_range() -> __swap_table_get()?
[ ... ]
> @@ -1206,50 +1229,146 @@ static unsigned int alloc_swap_scan_list(struct swap_info_struct *si,
[ ... ]
> +static int vswap_grow_clusters(struct swap_info_struct *si)
> {
[ ... ]
> + /*
> + * Publish the new clusters before they become reachable by offset.
> + * A zeroed page leaves them off-list with CLUSTER_FLAG_NONE, which
> + * is what move_cluster() expects.
> + */
> + WRITE_ONCE(si->nr_mapped_clusters, first + nr);
> + for (i = first; i < first + nr; i++) {
> + ci = &si->cluster_info[i];
> + spin_lock_init(&ci->lock);
> + INIT_LIST_HEAD(&ci->list);
> + spin_lock(&ci->lock);
> + move_cluster(si, ci, &si->free_clusters, CLUSTER_FLAG_FREE);
> + spin_unlock(&ci->lock);
> }
[Severity: High]
What happens if swap_cluster_readahead() observes the updated
si->nr_mapped_clusters in vswap_grow_clusters() before the loop finishes
running spin_lock_init(&ci->lock) on each new cluster?
In mm/swap_state.c, swap_cluster_readahead() bounds end_offset using
READ_ONCE(si->nr_mapped_clusters) and then locks each cluster in the range
via swap_cache_read_folio() -> __swap_cache_alloc_folio() ->
__swap_cache_alloc() -> swap_cluster_lock():
mm/swap_state.c:swap_cluster_readahead() {
...
max = si->max;
if (swap_is_vswap(si))
max = min(max, READ_ONCE(si->nr_mapped_clusters) *
SWAPFILE_CLUSTER);
if (end_offset >= max)
end_offset = max - 1;
blk_start_plug(&plug);
for (offset = start_offset; offset <= end_offset ; offset++) {
/* Ok, do the async read-ahead now */
ra_entry = swp_entry(swp_type(entry), offset);
folio = swap_cache_read_folio(&ctx, ra_entry, gfp_mask, mpol,
ilx, offset != entry_offset);
...
}
When a readahead window spans across a cluster boundary into the newly grown
range [first, first + nr) (or after cluster first is added to
si->free_clusters while clusters first + 1 .. first + nr - 1 are still being
initialized), swap_cluster_readahead() can call spin_lock(&ci->lock) on
si->cluster_info[i] before spin_lock_init(&ci->lock) has executed, or while
spin_lock_init(&ci->lock) is concurrently resetting the lock.
Should vswap_grow_clusters() initialize the new clusters before publishing
si->nr_mapped_clusters with smp_store_release() (paired with
smp_load_acquire() in readers), or initialize spin_lock_init(&ci->lock) and
INIT_LIST_HEAD(&ci->list) for the entire batch before making the new clusters
visible?
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261021060323.553433-1-nphamcs@gmail.com?part=12
next prev parent reply other threads:[~2026-10-22 6:19 UTC|newest]
Thread overview: 22+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-21 6:03 [PATCH v6 00/12] Virtual Swap Space (Swap Table Edition) Nhat Pham
2026-10-21 6:03 ` [PATCH v6 01/12] mm, swap: add virtual swap device infrastructure Nhat Pham
2026-10-21 6:03 ` [PATCH v6 02/12] mm, swap: support zswap and zero-filled swap pages as vswap backends Nhat Pham
2026-10-21 6:03 ` [PATCH v6 03/12] mm, swap: prepare the swap IO path for vswap Nhat Pham
2026-10-21 6:03 ` [PATCH v6 04/12] mm, swap: support physical swap as a vswap backend Nhat Pham
2026-10-22 6:19 ` sashiko-bot
2026-10-21 6:03 ` [PATCH v6 05/12] mm, swap: enable THP swapin for vswap entries Nhat Pham
2026-10-22 6:19 ` sashiko-bot
2026-10-21 6:03 ` [PATCH v6 06/12] mm, swap: write back vswap zswap entries to physical swap Nhat Pham
2026-10-22 6:19 ` sashiko-bot
2026-10-21 6:03 ` [PATCH v6 07/12] mm, swap: reclaim physical slots backing cache-only vswap entries Nhat Pham
2026-10-22 6:19 ` sashiko-bot
2026-10-21 6:03 ` [PATCH v6 08/12] mm, swap: only charge physical swap entries Nhat Pham
2026-10-22 6:19 ` sashiko-bot
2026-10-21 6:03 ` [PATCH v6 09/12] mm, swap: add debugfs counters for vswap Nhat Pham
2026-10-21 6:18 ` [PATCH v6 10/12] mm, swap: defer memcg_table allocation for physical swap clusters Nhat Pham
2026-10-22 6:19 ` sashiko-bot
2026-10-21 6:18 ` [PATCH v6 11/12] mm, swap: add vmstat and memcg counters for vswap Nhat Pham
2026-10-21 6:18 ` [RFC PATCH v6 12/12] mm, swap: back vswap clusters with a VM_SPARSE array Nhat Pham
2026-10-22 6:19 ` sashiko-bot [this message]
2026-10-22 2:36 ` [PATCH v6 00/12] Virtual Swap Space (Swap Table Edition) Gregory Price (Meta)
2026-10-22 17:03 ` 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=sashiko-outbox-181086@kernel.org \
--to=sashiko-bot@kernel.org \
--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=chengming.zhou@linux.dev \
--cc=chrisl@kernel.org \
--cc=corbet@lwn.net \
--cc=david@kernel.org \
--cc=gourry@gourry.net \
--cc=hannes@cmpxchg.org \
--cc=haowenchao22@gmail.com \
--cc=hughd@google.com \
--cc=kasong@tencent.com \
--cc=kernel-team@meta.com \
--cc=kunwu.chan@linux.dev \
--cc=liam@infradead.org \
--cc=linux-mm@kvack.org \
--cc=ljs@kernel.org \
--cc=mhocko@kernel.org \
--cc=mkoutny@suse.com \
--cc=muchun.song@linux.dev \
--cc=nphamcs@gmail.com \
--cc=qi.zheng@linux.dev \
--cc=rdunlap@infradead.org \
--cc=riel@surriel.com \
--cc=roman.gushchin@linux.dev \
--cc=rppt@kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=shakeel.butt@linux.dev \
--cc=shikemeng@huaweicloud.com \
--cc=skhan@linuxfoundation.org \
--cc=surenb@google.com \
--cc=tj@kernel.org \
--cc=vbabka@kernel.org \
--cc=weixugc@google.com \
--cc=yosry@kernel.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox