[PATCH RFC 11/13] mm/swap: perform sync discard on single device more proactively
From: Kairui Song via B4 Relay
Date: Mon Jul 13 2026 - 13:28:14 EST
From: Kairui Song <kasong@xxxxxxxxxxx>
Previous commit 9fb749cd15078 ("mm, swap: do not perform synchronous
discard during allocation") added a workaround-style global sync discard
when all devices are drained to prevent OOM. It wasn't an optimal
solution and in the discussion [1], it's preferred to discard more
proactively, and in the scope of a single device instead of globally.
That wasn't achievable due to the limitations of the previous swap
allocation design, or would have ended up too complex.
Now with the new workflow and swap cluster cache moved inside the device
scope, we can easily achieve that. So drop the old workaround and
implement a more proactive and simpler solution. Doing discard when the
free list is drained will prevent fragmentation better and avoid a
global discard scan.
Link: https://lore.kernel.org/all/CAMgjq7CsYhEjvtN85XGkrONYAJxve7gG593TFeOGV-oax++kWA@xxxxxxxxxxxxxx/ [1]
Signed-off-by: Kairui Song <kasong@xxxxxxxxxxx>
---
mm/swapfile.c | 88 ++++++++++++++++++++++-------------------------------------
1 file changed, 33 insertions(+), 55 deletions(-)
diff --git a/mm/swapfile.c b/mm/swapfile.c
index 01240c4c9db3..74a50c39da60 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -498,6 +498,26 @@ static long swap_usage_in_pages(struct swap_info_struct *si)
return atomic_long_read(&si->inuse_pages) & SWAP_USAGE_COUNTER_MASK;
}
+/*
+ * Serialize the allocation on single CPU or globally to avoid
+ * fragmentation and make the workflow easier to follow.
+ */
+static void swap_alloc_lock_device(struct swap_info_struct *si)
+{
+ if (si->flags & SWP_SOLIDSTATE)
+ local_lock(&si->percpu_cluster->lock);
+ else
+ spin_lock(&si->global_cluster->lock);
+}
+
+static void swap_alloc_unlock_device(struct swap_info_struct *si)
+{
+ if (si->flags & SWP_SOLIDSTATE)
+ local_unlock(&si->percpu_cluster->lock);
+ else
+ spin_unlock(&si->global_cluster->lock);
+}
+
/* Reclaim the swap entry anyway if possible */
#define TTRS_ANYWAY 0x1
/*
@@ -884,10 +904,7 @@ swap_cluster_populate(struct swap_info_struct *si,
* the potential recursive allocation is limited.
*/
spin_unlock(&ci->lock);
- if (si->flags & SWP_SOLIDSTATE)
- local_unlock(&si->percpu_cluster->lock);
- else
- spin_unlock(&si->global_cluster->lock);
+ swap_alloc_unlock_device(si);
ret = swap_cluster_alloc_table(ci, __GFP_HIGH | __GFP_NOMEMALLOC |
GFP_KERNEL);
@@ -900,10 +917,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.
*/
- if (si->flags & SWP_SOLIDSTATE)
- local_lock(&si->percpu_cluster->lock);
- else
- spin_lock(&si->global_cluster->lock);
+ swap_alloc_lock_device(si);
spin_lock(&ci->lock);
if (ret) {
@@ -1439,15 +1453,12 @@ static unsigned long cluster_alloc_swap_entry(struct swap_info_struct *si,
if (order && !(si->flags & SWP_BLKDEV))
return 0;
- if (si->flags & SWP_SOLIDSTATE) {
- /* Fast path using per CPU cluster */
- local_lock(&si->percpu_cluster->lock);
+restart:
+ swap_alloc_lock_device(si);
+ if (si->flags & SWP_SOLIDSTATE)
offset = __this_cpu_read(si->percpu_cluster->next[order]);
- } else {
- /* Serialize HDD SWAP allocation for each device. */
- spin_lock(&si->global_cluster->lock);
+ else
offset = si->global_cluster->next[order];
- }
if (offset != SWAP_ENTRY_INVALID) {
ci = swap_cluster_lock(si, offset);
@@ -1471,6 +1482,12 @@ static unsigned long cluster_alloc_swap_entry(struct swap_info_struct *si,
found = alloc_swap_scan_list(si, &si->free_clusters, folio, false);
if (found)
goto done;
+
+ if (!list_empty(&si->discard_clusters)) {
+ swap_alloc_unlock_device(si);
+ swap_do_scheduled_discard(si);
+ goto restart;
+ }
}
if (order < PMD_ORDER) {
@@ -1519,10 +1536,7 @@ static unsigned long cluster_alloc_swap_entry(struct swap_info_struct *si,
goto done;
}
done:
- if (si->flags & SWP_SOLIDSTATE)
- local_unlock(&si->percpu_cluster->lock);
- else
- spin_unlock(&si->global_cluster->lock);
+ swap_alloc_unlock_device(si);
return found;
}
@@ -1711,36 +1725,6 @@ static int swap_alloc_entry(struct folio *folio)
return ret;
}
-/*
- * Discard pending clusters in a synchronized way when under high pressure.
- * Return: true if any cluster is discarded.
- */
-static bool swap_sync_discard(void)
-{
- bool ret = false;
- struct swap_info_struct *si, *next;
-
- percpu_down_read(&swapon_rwsem);
-start_over:
- plist_for_each_entry_safe(si, next, &swap_active_head, list) {
- percpu_up_read(&swapon_rwsem);
- if (get_swap_device_info(si)) {
- if (si->flags & SWP_PAGE_DISCARD)
- ret = swap_do_scheduled_discard(si);
- put_swap_device(si);
- }
- if (ret)
- return true;
-
- percpu_down_read(&swapon_rwsem);
- if (plist_node_empty(&next->list))
- goto start_over;
- }
- percpu_up_read(&swapon_rwsem);
-
- return false;
-}
-
static int swap_extend_table_alloc(struct swap_info_struct *si,
struct swap_cluster_info *ci,
unsigned int ci_off, gfp_t gfp)
@@ -2045,14 +2029,8 @@ int folio_alloc_swap(struct folio *folio)
}
}
-again:
ret = swap_alloc_entry(folio);
- if (!order && unlikely(!folio_test_swapcache(folio))) {
- if (swap_sync_discard())
- goto again;
- }
-
/* Need to call this even if allocation failed, for MEMCG_SWAP_FAIL. */
if (unlikely(mem_cgroup_try_charge_swap(folio)))
swap_cache_del_folio(folio);
--
2.55.0