* [PATCH v2 1/4] mm/zswap: drop the stale zswap entry of a zero folio
2026-11-03 16:18 [PATCH v2 0/4] mm/swap, zswap: fix stale zswap data and swap-in bugs Kairui Song via B4 Relay
@ 2026-11-03 16:18 ` Kairui Song via B4 Relay
2026-11-03 16:18 ` [PATCH v2 2/4] mm, swap: fix large swap-in across swap file extents Kairui Song via B4 Relay
` (2 subsequent siblings)
3 siblings, 0 replies; 8+ messages in thread
From: Kairui Song via B4 Relay @ 2026-11-03 16:18 UTC (permalink / raw)
To: linux-mm
Cc: Andrew Morton, Chris Li, Kairui Song, Kemeng Shi, Nhat Pham,
Baoquan He, Barry Song, Youngjun Park, Usama Arif,
Chengming Zhou, Yosry Ahmed, Johannes Weiner, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Chuanhua Han, Mani Tofigh,
NeilBrown, Christoph Hellwig, linux-kernel, Josef Bacik, stable
From: Josef Bacik <josef@toxicpanda.com>
A folio can stay in the swap cache after zswap_store() has stored it,
when a swapin fault gets a reference on it before reclaim can remove it.
do_swap_page() maps an exclusive folio writable and, for a read fault,
keeps it in the swap cache, so it can be modified and swapped out again
to the same slot. zswap_store() handles this, it replaces the old entry,
or drops it if the store fails or zswap got disabled, "otherwise,
writeback could overwrite the new data in the swapfile".
swap_writeout() does not get as far as zswap_store() if the folio is
zero-filled now. It only sets the zero flag of the slot, and the zswap
entry with the previous contents stays in the tree and on the LRU:
reclaim fault zswap shrinker
zswap_store(D1)
swap cache hit,
map, write zeroes
swap_writeout()
zero flag set
folio reclaimed
zswap_writeback_entry()
swap_cache_alloc_folio()
decompress D1, uptodate
swap cache hit:
maps D1
Swapin looks at the zero flag first, but only when it has to read the
folio. The folio that zswap writeback leaves in the swap cache is up to
date as far as anybody can tell, so the process gets its old data back
instead of the zeroes it has written, until that folio is reclaimed.
Invalidate the zswap entries in the zero-filled case as well. The folio
is locked and in the swap cache, which keeps zswap writeback away.
Fixes: 0ca0c24e3211 ("mm: store zero pages to be swapped out in a bitmap")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
[kasong@tencent.com: fix zswap_invalidate() arguments]
Reviewed-by: Johannes Weiner <hannes@cmpxchg.org>
Reviewed-by: Nhat Pham <nphamcs@gmail.com>
Reviewed-by: Yosry Ahmed <yosry@kernel.org>
Signed-off-by: Kairui Song <kasong@tencent.com>
---
mm/page_io.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/mm/page_io.c b/mm/page_io.c
index c6824fcd483e..65681eed9c80 100644
--- a/mm/page_io.c
+++ b/mm/page_io.c
@@ -224,7 +224,17 @@ int swap_writeout(struct swap_io_ctx *ctx, struct folio *folio)
* acquired internally by swap_zeromap_folio_set/clear.
*/
if (is_folio_zero_filled(folio)) {
+ swp_entry_t entry = folio->swap;
+
swap_zeromap_folio_set(folio);
+
+ /*
+ * The folio may have been stored in zswap before and dirtied
+ * again while it stayed in the swap cache. Drop the old copy,
+ * or zswap writeback would bring it back into the swap cache.
+ */
+ zswap_invalidate(swp_type(entry), swp_offset(entry),
+ folio_nr_pages(folio));
goto out_unlock;
}
--
2.56.0
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH v2 2/4] mm, swap: fix large swap-in across swap file extents
2026-11-03 16:18 [PATCH v2 0/4] mm/swap, zswap: fix stale zswap data and swap-in bugs Kairui Song via B4 Relay
2026-11-03 16:18 ` [PATCH v2 1/4] mm/zswap: drop the stale zswap entry of a zero folio Kairui Song via B4 Relay
@ 2026-11-03 16:18 ` Kairui Song via B4 Relay
2026-11-03 16:18 ` [PATCH v2 3/4] mm/swap: account synchronous swap reads as memory stall Kairui Song via B4 Relay
2026-11-03 16:18 ` [PATCH v2 4/4] mm/swap: keep the swap-in pressure window open across a batch Kairui Song via B4 Relay
3 siblings, 0 replies; 8+ messages in thread
From: Kairui Song via B4 Relay @ 2026-11-03 16:18 UTC (permalink / raw)
To: linux-mm
Cc: Andrew Morton, Chris Li, Kairui Song, Kemeng Shi, Nhat Pham,
Baoquan He, Barry Song, Youngjun Park, Usama Arif,
Chengming Zhou, Yosry Ahmed, Johannes Weiner, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Chuanhua Han, Mani Tofigh,
NeilBrown, Christoph Hellwig, linux-kernel, Chris Mason, stable
From: Chris Mason <mason@kernel.org>
A page fault can give an unprivileged process blocks of other users'
files instead of its own swapped-out data. This affects systems with a
swap file on a synchronous block device, such as brd or zram. For an
anonymous mapping it also takes a huge page size below the PMD size
(mTHP), such as 64 KiB, that is enabled for anonymous memory.
A swap area is divided into slots of one page each. A swap file is a
regular file, so adjacent swap slots can lie in two extents, far apart
on disk. A folio is read from swap with a single bio that starts at the
sector of the folio's first slot. So all slots of a large folio must be
contiguous on disk.
The swap allocator knows this: it only hands out a run of slots for a
large folio if the swap area is a block device (SWP_BLKDEV).
The swap-in path does not. On a synchronous device (SWP_SYNCHRONOUS_IO),
one that always completes I/O in the submitter's context, do_swap_page()
can read several swapped-out pages as one large folio (a large swap-in).
thp_swapin_suitable_orders() makes that decision, and looks only at the
swap offsets (the slot numbers) in the page table.
Pages that were swapped out one at a time can end up in consecutive,
aligned slots, and are then read as one large folio. If the extent of
the first slot ends inside that folio, the rest of the folio is filled
from the blocks that follow the extent on disk. These blocks can belong
to other files.
Reproduced with a fragmented swap file on brd and 64 KiB folios enabled.
After single pages were swapped out, a fault returned blocks of a
root-owned file with mode 0600.
Fix this by allowing a large swap-in only when SWP_BLKDEV is set.
Known gaps:
- A swap file on zram, brd, pmem or btt is now read one page at a time,
even where its slots lie in one extent. Its swpin counters in
/sys/kernel/mm/transparent_hugepage/hugepages-*/stats stay at 0.
- There could be devices with SWP_SYNCHRONOUS_IO but not
SWP_BLKDEV, so the new check would have to let that device pass.
Fixes: 242d12c98174 ("mm: support large folios swap-in for sync io devices")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Chris Mason <mason@kernel.org>
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
[kasong@tencent.com: simplify the note about the unmerged xswap patch]
Reviewed-by: Nhat Pham <nphamcs@gmail.com>
Signed-off-by: Kairui Song <kasong@tencent.com>
---
mm/memory.c | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/mm/memory.c b/mm/memory.c
index b65b56fe9714..944f57153c89 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -4863,7 +4863,8 @@ static inline unsigned long thp_swap_suitable_orders(pgoff_t swp_offset,
return orders;
}
-static unsigned long thp_swapin_suitable_orders(struct vm_fault *vmf)
+static unsigned long thp_swapin_suitable_orders(struct swap_info_struct *si,
+ struct vm_fault *vmf)
{
struct vm_area_struct *vma = vmf->vma;
unsigned long orders;
@@ -4888,6 +4889,14 @@ static unsigned long thp_swapin_suitable_orders(struct vm_fault *vmf)
if (!zswap_never_enabled())
return 0;
+ /*
+ * Neighbouring slots of a swap file can lie in two extents, and a folio
+ * is read with one bio. The flag is set in swapon() and stays, so the
+ * race is benign.
+ */
+ if (!data_race(si->flags & SWP_BLKDEV))
+ return 0;
+
entry = softleaf_from_pte(vmf->orig_pte);
/*
* Get a list of all the (large) orders below PMD_ORDER that are enabled
@@ -4924,7 +4933,8 @@ static unsigned long thp_swapin_suitable_orders(struct vm_fault *vmf)
return orders;
}
#else /* !CONFIG_TRANSPARENT_HUGEPAGE */
-static unsigned long thp_swapin_suitable_orders(struct vm_fault *vmf)
+static unsigned long thp_swapin_suitable_orders(struct swap_info_struct *si,
+ struct vm_fault *vmf)
{
return 0;
}
@@ -5070,7 +5080,7 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
/* Swapin bypasses readahead for SWP_SYNCHRONOUS_IO devices */
if (data_race(si->flags & SWP_SYNCHRONOUS_IO))
folio = swapin_sync(entry, GFP_HIGHUSER_MOVABLE,
- thp_swapin_suitable_orders(vmf) | BIT(0),
+ thp_swapin_suitable_orders(si, vmf) | BIT(0),
vmf, NULL, 0);
else
folio = swapin_readahead(entry, GFP_HIGHUSER_MOVABLE, vmf);
--
2.56.0
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH v2 3/4] mm/swap: account synchronous swap reads as memory stall
2026-11-03 16:18 [PATCH v2 0/4] mm/swap, zswap: fix stale zswap data and swap-in bugs Kairui Song via B4 Relay
2026-11-03 16:18 ` [PATCH v2 1/4] mm/zswap: drop the stale zswap entry of a zero folio Kairui Song via B4 Relay
2026-11-03 16:18 ` [PATCH v2 2/4] mm, swap: fix large swap-in across swap file extents Kairui Song via B4 Relay
@ 2026-11-03 16:18 ` Kairui Song via B4 Relay
2026-11-03 18:26 ` Johannes Weiner
2026-11-03 19:46 ` Nhat Pham
2026-11-03 16:18 ` [PATCH v2 4/4] mm/swap: keep the swap-in pressure window open across a batch Kairui Song via B4 Relay
3 siblings, 2 replies; 8+ messages in thread
From: Kairui Song via B4 Relay @ 2026-11-03 16:18 UTC (permalink / raw)
To: linux-mm
Cc: Andrew Morton, Chris Li, Kairui Song, Kemeng Shi, Nhat Pham,
Baoquan He, Barry Song, Youngjun Park, Usama Arif,
Chengming Zhou, Yosry Ahmed, Johannes Weiner, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Chuanhua Han, Mani Tofigh,
NeilBrown, Christoph Hellwig, linux-kernel, Josef Bacik
From: Josef Bacik <josef@toxicpanda.com>
Swap-ins from SWP_SYNCHRONOUS_IO devices such as zram are no longer
counted in PSI memory pressure or in the swapin and thrashing delays of
delay accounting. Refaults of the workingset from zram, the main input
to userspace OOM handlers like lmkd and systemd-oomd on zram systems,
become invisible in /proc/pressure/memory.
swap_read_folio() opens the accounting window with
psi_memstall_enter() and delayacct_swapin_start() and, per its comment,
means to count the submission time. For synchronous devices the
submission is the entire read: swap_bdev_submit_read() does
submit_bio_wait() and completes the folio. Since reads are batched in
a swap_iocb, swap_read_folio() only queues the folio with
swap_add_folio(), and the bio is submitted later by swap_read_submit()
in the caller, e.g. swapin_sync(), after the window has been closed.
By then the folio is uptodate and unlocked, so the fault does not wait
on the folio lock either and the read time is not counted anywhere.
Fix by submitting synchronous reads from within swap_read_folio(), as
was done before the batching, rather than from the caller. This gives
up batching of readahead for synchronous devices, which do their I/O
inline anyway, in exchange for keeping the accounting simple and in one
place.
Fixes: dda8fb68b590 ("mm/swap: also use struct swap_iocb for block I/O")
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
Signed-off-by: Kairui Song <kasong@tencent.com>
---
mm/page_io.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/mm/page_io.c b/mm/page_io.c
index 65681eed9c80..83ed0246ebfc 100644
--- a/mm/page_io.c
+++ b/mm/page_io.c
@@ -500,6 +500,13 @@ void swap_read_folio(struct swap_io_ctx *ctx, struct folio *folio)
zswap_folio_swapin(folio);
swap_add_folio(ctx, folio, READ);
+ /*
+ * Synchronous devices do the whole read at submission time, submit
+ * it here so that it is accounted as memory stall and delay.
+ */
+ if (synchronous)
+ swap_read_submit(ctx);
+
finish:
if (workingset) {
delayacct_thrashing_end(&in_thrashing);
--
2.56.0
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH v2 3/4] mm/swap: account synchronous swap reads as memory stall
2026-11-03 16:18 ` [PATCH v2 3/4] mm/swap: account synchronous swap reads as memory stall Kairui Song via B4 Relay
@ 2026-11-03 18:26 ` Johannes Weiner
2026-11-03 19:46 ` Nhat Pham
1 sibling, 0 replies; 8+ messages in thread
From: Johannes Weiner @ 2026-11-03 18:26 UTC (permalink / raw)
To: Kairui Song via B4 Relay
Cc: linux-mm, Andrew Morton, Chris Li, Kairui Song, Kemeng Shi,
Nhat Pham, Baoquan He, Barry Song, Youngjun Park, Usama Arif,
Chengming Zhou, Yosry Ahmed, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Chuanhua Han, Mani Tofigh,
NeilBrown, Christoph Hellwig, linux-kernel, Josef Bacik
On Wed, Nov 04, 2026 at 12:18:16AM +0800, Kairui Song via B4 Relay wrote:
> From: Josef Bacik <josef@toxicpanda.com>
>
> Swap-ins from SWP_SYNCHRONOUS_IO devices such as zram are no longer
> counted in PSI memory pressure or in the swapin and thrashing delays of
> delay accounting. Refaults of the workingset from zram, the main input
> to userspace OOM handlers like lmkd and systemd-oomd on zram systems,
> become invisible in /proc/pressure/memory.
>
> swap_read_folio() opens the accounting window with
> psi_memstall_enter() and delayacct_swapin_start() and, per its comment,
> means to count the submission time. For synchronous devices the
> submission is the entire read: swap_bdev_submit_read() does
> submit_bio_wait() and completes the folio. Since reads are batched in
> a swap_iocb, swap_read_folio() only queues the folio with
> swap_add_folio(), and the bio is submitted later by swap_read_submit()
> in the caller, e.g. swapin_sync(), after the window has been closed.
> By then the folio is uptodate and unlocked, so the fault does not wait
> on the folio lock either and the read time is not counted anywhere.
>
> Fix by submitting synchronous reads from within swap_read_folio(), as
> was done before the batching, rather than from the caller. This gives
> up batching of readahead for synchronous devices, which do their I/O
> inline anyway, in exchange for keeping the accounting simple and in one
> place.
>
> Fixes: dda8fb68b590 ("mm/swap: also use struct swap_iocb for block I/O")
> Assisted-by: LLM
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>
> Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
> Signed-off-by: Kairui Song <kasong@tencent.com>
Reviewed-by: Johannes Weiner <hannes@cmpxchg.org>
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH v2 3/4] mm/swap: account synchronous swap reads as memory stall
2026-11-03 16:18 ` [PATCH v2 3/4] mm/swap: account synchronous swap reads as memory stall Kairui Song via B4 Relay
2026-11-03 18:26 ` Johannes Weiner
@ 2026-11-03 19:46 ` Nhat Pham
1 sibling, 0 replies; 8+ messages in thread
From: Nhat Pham @ 2026-11-03 19:46 UTC (permalink / raw)
To: kasong
Cc: linux-mm, Andrew Morton, Chris Li, Kemeng Shi, Baoquan He,
Barry Song, Youngjun Park, Usama Arif, Chengming Zhou,
Yosry Ahmed, Johannes Weiner, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Chuanhua Han, Mani Tofigh,
NeilBrown, Christoph Hellwig, linux-kernel, Josef Bacik
On Tue, Nov 3, 2026 at 9:18 AM Kairui Song via B4 Relay
<devnull+kasong.tencent.com@kernel.org> wrote:
>
> From: Josef Bacik <josef@toxicpanda.com>
>
> Swap-ins from SWP_SYNCHRONOUS_IO devices such as zram are no longer
> counted in PSI memory pressure or in the swapin and thrashing delays of
> delay accounting. Refaults of the workingset from zram, the main input
> to userspace OOM handlers like lmkd and systemd-oomd on zram systems,
> become invisible in /proc/pressure/memory.
>
> swap_read_folio() opens the accounting window with
> psi_memstall_enter() and delayacct_swapin_start() and, per its comment,
> means to count the submission time. For synchronous devices the
> submission is the entire read: swap_bdev_submit_read() does
> submit_bio_wait() and completes the folio. Since reads are batched in
> a swap_iocb, swap_read_folio() only queues the folio with
> swap_add_folio(), and the bio is submitted later by swap_read_submit()
> in the caller, e.g. swapin_sync(), after the window has been closed.
> By then the folio is uptodate and unlocked, so the fault does not wait
> on the folio lock either and the read time is not counted anywhere.
>
> Fix by submitting synchronous reads from within swap_read_folio(), as
> was done before the batching, rather than from the caller. This gives
> up batching of readahead for synchronous devices, which do their I/O
> inline anyway, in exchange for keeping the accounting simple and in one
> place.
>
> Fixes: dda8fb68b590 ("mm/swap: also use struct swap_iocb for block I/O")
> Assisted-by: LLM
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>
> Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---
> mm/page_io.c | 7 +++++++
> 1 file changed, 7 insertions(+)
>
> diff --git a/mm/page_io.c b/mm/page_io.c
> index 65681eed9c80..83ed0246ebfc 100644
> --- a/mm/page_io.c
> +++ b/mm/page_io.c
> @@ -500,6 +500,13 @@ void swap_read_folio(struct swap_io_ctx *ctx, struct folio *folio)
> zswap_folio_swapin(folio);
> swap_add_folio(ctx, folio, READ);
>
> + /*
> + * Synchronous devices do the whole read at submission time, submit
> + * it here so that it is accounted as memory stall and delay.
> + */
> + if (synchronous)
> + swap_read_submit(ctx);
> +
Reviewed-by: Nhat Pham <nphamcs@gmail.com>
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v2 4/4] mm/swap: keep the swap-in pressure window open across a batch
2026-11-03 16:18 [PATCH v2 0/4] mm/swap, zswap: fix stale zswap data and swap-in bugs Kairui Song via B4 Relay
` (2 preceding siblings ...)
2026-11-03 16:18 ` [PATCH v2 3/4] mm/swap: account synchronous swap reads as memory stall Kairui Song via B4 Relay
@ 2026-11-03 16:18 ` Kairui Song via B4 Relay
2026-11-03 19:45 ` Nhat Pham
3 siblings, 1 reply; 8+ messages in thread
From: Kairui Song via B4 Relay @ 2026-11-03 16:18 UTC (permalink / raw)
To: linux-mm
Cc: Andrew Morton, Chris Li, Kairui Song, Kemeng Shi, Nhat Pham,
Baoquan He, Barry Song, Youngjun Park, Usama Arif,
Chengming Zhou, Yosry Ahmed, Johannes Weiner, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Chuanhua Han, Mani Tofigh,
NeilBrown, Christoph Hellwig, linux-kernel
From: Johannes Weiner <hannes@cmpxchg.org>
For asynchronous block devices, swap reads are batched and submitted later
since commit dda8fb68b590 ("mm/swap: also use struct swap_iocb for block
I/O"), but the caller submits the batch after the pressure window in
swap_read_folio() has closed. Batching is cheap, the submission is the
part that can block, and that is no longer counted as pressure since then.
Keep the pressure window open across the batch instead: swap_read_folio()
opens it on the first folio, and swap_read_submit() closes it after the
submission, also when the batch ends up with no folio queued. Each batch
is then billed for its own read.
A single workingset refault marks its whole batch as stalled, which is
fine here: the batch is submitted as a whole, so its submission holds
up that refault too. Before the batching, a blocking submission was billed
only to the folio that issued it, even when it also delayed the refaults
queued behind it in the same task, so this might be more accurate rather
than less. Synchronous reads are already one folio per batch after the
previous patch, which keeps their attribution tight.
The same hole has been there for a swap file on a file system that submits
through swap_fs_prepare_rw(), since commit 5169b844b7dd ("mm: submit
multipage reads for SWP_FS_OPS swap-space"), which started batching its
reads and deferring the submission to the caller, so this covers it there
as well.
Fixes: dda8fb68b590 ("mm/swap: also use struct swap_iocb for block I/O")
Fixes: 5169b844b7dd ("mm: submit multipage reads for SWP_FS_OPS swap-space")
Signed-off-by: Johannes Weiner <hannes@cmpxchg.org>
Link: https://lore.kernel.org/linux-mm/auOz00OGB_8dgniC@cmpxchg.org/
[kasong@tencent.com: added commit message and slight tweaks]
Signed-off-by: Kairui Song <kasong@tencent.com>
---
include/linux/swap_ops.h | 10 ++++++
mm/page_io.c | 79 ++++++++++++++++++++++++++++++------------------
2 files changed, 60 insertions(+), 29 deletions(-)
diff --git a/include/linux/swap_ops.h b/include/linux/swap_ops.h
index 57ac6c703f68..d57b95931cd9 100644
--- a/include/linux/swap_ops.h
+++ b/include/linux/swap_ops.h
@@ -17,6 +17,16 @@ struct swap_iocb {
struct swap_io_ctx {
struct swap_iocb *sio;
struct swap_info_struct *sis;
+
+ /*
+ * Pressure window: opened by swap_read_folio() on the first folio
+ * of a batch, closed by swap_read_submit(). Mirrors per-task
+ * state, so the task that opened it must be the one to close it.
+ */
+ bool workingset;
+ bool in_delayacct;
+ bool in_thrashing;
+ unsigned long in_memstall;
};
/*
diff --git a/mm/page_io.c b/mm/page_io.c
index 83ed0246ebfc..a57cdcf2892d 100644
--- a/mm/page_io.c
+++ b/mm/page_io.c
@@ -350,13 +350,11 @@ static void swap_add_folio(struct swap_io_ctx *ctx, struct folio *folio, int rw)
struct swap_info_struct *sis = __swap_entry_to_info(folio->swap);
struct swap_iocb *sio = ctx->sio;
- if (sio && !swap_can_merge(ctx, folio, rw)) {
- if (rw == WRITE)
- swap_write_submit(ctx);
- else
- swap_read_submit(ctx);
- sio = ctx->sio;
- }
+ /*
+ * Callers flush a batch this folio can't join before adding it,
+ * see swap_read_folio() and __swap_writeout().
+ */
+ VM_WARN_ON_ONCE(sio && !swap_can_merge(ctx, folio, rw));
if (!sio) {
ctx->sis = sis;
@@ -399,6 +397,14 @@ void __swap_writeout(struct swap_io_ctx *ctx, struct folio *folio)
folio_start_writeback(folio);
folio_unlock(folio);
+
+ /*
+ * Flush a batch this folio can't join before adding it, as in
+ * swap_read_folio(). But the write path opens no pressure window,
+ * so there is nothing to order this against.
+ */
+ if (ctx->sio && !swap_can_merge(ctx, folio, WRITE))
+ swap_write_submit(ctx);
swap_add_folio(ctx, folio, WRITE);
}
@@ -470,31 +476,44 @@ void swap_read_folio(struct swap_io_ctx *ctx, struct folio *folio)
struct swap_info_struct *sis = __swap_entry_to_info(folio->swap);
bool synchronous = sis->flags & SWP_SYNCHRONOUS_IO;
bool workingset = folio_test_workingset(folio);
- unsigned long pflags;
- bool in_thrashing;
VM_BUG_ON_FOLIO(!folio_test_swapcache(folio) && !synchronous, folio);
VM_BUG_ON_FOLIO(!folio_test_locked(folio), folio);
VM_BUG_ON_FOLIO(folio_test_uptodate(folio), folio);
+ /*
+ * A batch this folio can't join is submitted before the pressure
+ * window opens, so that each batch is billed for its own read
+ * rather than for the next folio's lookup and decompression.
+ */
+ if (ctx->sio && !swap_can_merge(ctx, folio, READ))
+ swap_read_submit(ctx);
+
/*
* Count submission time as memory stall and delay. When the device
* is congested, or the submitting cgroup IO-throttled, submission
- * can be a significant part of overall IO time.
+ * can be a significant part of overall IO time. Reads are batched and
+ * submitted later, so both windows stay open on @ctx, and are closed
+ * by swap_read_submit() after the submission, also when the batch
+ * ends up with no folio queued.
*/
- if (workingset) {
- delayacct_thrashing_start(&in_thrashing);
- psi_memstall_enter(&pflags);
+ if (!ctx->in_delayacct) {
+ delayacct_swapin_start();
+ ctx->in_delayacct = true;
+ }
+ if (workingset && !ctx->workingset) {
+ delayacct_thrashing_start(&ctx->in_thrashing);
+ psi_memstall_enter(&ctx->in_memstall);
+ ctx->workingset = true;
}
- delayacct_swapin_start();
if (swap_read_folio_zeromap(folio)) {
folio_unlock(folio);
- goto finish;
+ return;
}
if (zswap_load(folio) != -ENOENT)
- goto finish;
+ return;
/* We have to read from slower devices. Increase zswap protection. */
zswap_folio_swapin(folio);
@@ -506,13 +525,6 @@ void swap_read_folio(struct swap_io_ctx *ctx, struct folio *folio)
*/
if (synchronous)
swap_read_submit(ctx);
-
-finish:
- if (workingset) {
- delayacct_thrashing_end(&in_thrashing);
- psi_memstall_leave(&pflags);
- }
- delayacct_swapin_end();
}
static void swap_write_end(struct swap_iocb *sio, bool failed)
@@ -725,10 +737,19 @@ void swap_write_submit(struct swap_io_ctx *ctx)
void swap_read_submit(struct swap_io_ctx *ctx)
{
- if (!ctx->sio)
- return;
- count_vm_events(NRSWPIN, 1);
- ctx->sis->ops->submit_read(ctx);
- ctx->sio = NULL;
- ctx->sis = NULL;
+ if (ctx->sio) {
+ count_vm_events(NRSWPIN, 1);
+ ctx->sis->ops->submit_read(ctx);
+ ctx->sio = NULL;
+ ctx->sis = NULL;
+ }
+ if (ctx->workingset) {
+ delayacct_thrashing_end(&ctx->in_thrashing);
+ psi_memstall_leave(&ctx->in_memstall);
+ ctx->workingset = false;
+ }
+ if (ctx->in_delayacct) {
+ delayacct_swapin_end();
+ ctx->in_delayacct = false;
+ }
}
--
2.56.0
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH v2 4/4] mm/swap: keep the swap-in pressure window open across a batch
2026-11-03 16:18 ` [PATCH v2 4/4] mm/swap: keep the swap-in pressure window open across a batch Kairui Song via B4 Relay
@ 2026-11-03 19:45 ` Nhat Pham
0 siblings, 0 replies; 8+ messages in thread
From: Nhat Pham @ 2026-11-03 19:45 UTC (permalink / raw)
To: kasong
Cc: linux-mm, Andrew Morton, Chris Li, Kemeng Shi, Baoquan He,
Barry Song, Youngjun Park, Usama Arif, Chengming Zhou,
Yosry Ahmed, Johannes Weiner, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Chuanhua Han, Mani Tofigh,
NeilBrown, Christoph Hellwig, linux-kernel
On Tue, Nov 3, 2026 at 9:18 AM Kairui Song via B4 Relay
<devnull+kasong.tencent.com@kernel.org> wrote:
>
> From: Johannes Weiner <hannes@cmpxchg.org>
>
> For asynchronous block devices, swap reads are batched and submitted later
> since commit dda8fb68b590 ("mm/swap: also use struct swap_iocb for block
> I/O"), but the caller submits the batch after the pressure window in
> swap_read_folio() has closed. Batching is cheap, the submission is the
> part that can block, and that is no longer counted as pressure since then.
>
> Keep the pressure window open across the batch instead: swap_read_folio()
> opens it on the first folio, and swap_read_submit() closes it after the
> submission, also when the batch ends up with no folio queued. Each batch
> is then billed for its own read.
>
> A single workingset refault marks its whole batch as stalled, which is
> fine here: the batch is submitted as a whole, so its submission holds
> up that refault too. Before the batching, a blocking submission was billed
> only to the folio that issued it, even when it also delayed the refaults
> queued behind it in the same task, so this might be more accurate rather
> than less. Synchronous reads are already one folio per batch after the
> previous patch, which keeps their attribution tight.
>
> The same hole has been there for a swap file on a file system that submits
> through swap_fs_prepare_rw(), since commit 5169b844b7dd ("mm: submit
> multipage reads for SWP_FS_OPS swap-space"), which started batching its
> reads and deferring the submission to the caller, so this covers it there
> as well.
>
> Fixes: dda8fb68b590 ("mm/swap: also use struct swap_iocb for block I/O")
> Fixes: 5169b844b7dd ("mm: submit multipage reads for SWP_FS_OPS swap-space")
> Signed-off-by: Johannes Weiner <hannes@cmpxchg.org>
> Link: https://lore.kernel.org/linux-mm/auOz00OGB_8dgniC@cmpxchg.org/
> [kasong@tencent.com: added commit message and slight tweaks]
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---
> include/linux/swap_ops.h | 10 ++++++
> mm/page_io.c | 79 ++++++++++++++++++++++++++++++------------------
> 2 files changed, 60 insertions(+), 29 deletions(-)
Reviewed-by: Nhat Pham <nphamcs@gmail.com>
^ permalink raw reply [flat|nested] 8+ messages in thread