[PATCH v5 1/2] f2fs: complete dropbehind write bios in task context

From: Wenjie Qi

Date: Tue Sep 08 2026 - 00:00:57 EST


Buffered RWF_DONTCACHE writes invalidate dropbehind folios at
writeback completion. Mark these bios BIO_COMPLETE_IN_TASK so the block
layer runs F2FS completion in task context when needed.

Normal folios can safely share a bio that needs task-context
completion: they do not carry the dropbehind flag and are not invalidated
at their own writeback completion. Follow iomap and allow otherwise
mergeable normal and dropbehind folios to share IPU and OPU bios.

If a dropbehind folio joins an existing bio, set
BIO_COMPLETE_IN_TASK after the add succeeds. Classify the original
page-cache folio rather than an encrypted or compressed replacement folio.

Keep the existing large-ATC completion path unchanged.

Signed-off-by: Wenjie Qi <qiwenjie@xxxxxxxxxx>
---
An F2FS QEMU A/B ran 80 untraced 1-GiB formal cases: ten
counterbalanced pairs for each OPU/IPU and 4-KiB/64-KiB profile.

The primary write-plus-fdatasync mixed/segregated ratios were:

Path I/O Mixed/segregated Interpretation
OPU 4 KiB 1.133 Mixed was 13.3% faster
OPU 64 KiB 1.021 Mixed was 2.1% faster
IPU 4 KiB 4.469 Mixed was 4.47x as fast
IPU 64 KiB 1.063 Mixed was 6.3% faster

A separate 64-MiB IPU/4-KiB trace observed 16,384 segregated DATA bios,
all 4 KiB, versus 117 mixed DATA bios with a maximum of 13.21 MiB. Deferred
bios were 8,192 versus 117.

Changes since v4:

- allow normal and dropbehind folios to share IPU and OPU bios;
- set BIO_COMPLETE_IN_TASK monotonically after a dropbehind folio is
successfully added to an existing bio;
- add focused segregated/mixed QEMU results.

fs/f2fs/data.c | 6 ++++++
1 file changed, 6 insertions(+)

diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c
index ebb0275aa8f..fc48a752fe9 100644
--- a/fs/f2fs/data.c
+++ b/fs/f2fs/data.c
@@ -503,6 +503,8 @@ static struct bio *__bio_alloc(struct f2fs_io_info *fio, int npages)
bio = bio_alloc_bioset(bdev, npages,
fio->op | fio->op_flags | f2fs_io_flags(fio),
GFP_NOIO, &f2fs_bioset);
+ if (!is_read_io(fio->op) && folio_test_dropbehind(fio->folio))
+ bio_set_flag(bio, BIO_COMPLETE_IN_TASK);
bio->bi_iter.bi_sector = sector;
if (is_read_io(fio->op)) {
bio->bi_end_io = f2fs_read_end_io;
@@ -865,6 +867,8 @@ static int add_ipu_page(struct f2fs_io_info *fio, struct bio **bio,
fio_folio->mapping->host,
fio_folio->index, fio) &&
bio_add_folio(*bio, folio, folio_size(folio), 0)) {
+ if (folio_test_dropbehind(fio->folio))
+ bio_set_flag(*bio, BIO_COMPLETE_IN_TASK);
ret = 0;
break;
}
@@ -1100,6 +1104,8 @@ void f2fs_submit_page_write(struct f2fs_io_info *fio)
__submit_merged_bio(io);
goto alloc_new;
}
+ if (folio_test_dropbehind(fio->folio))
+ bio_set_flag(io->bio, BIO_COMPLETE_IN_TASK);

if (fio->io_wbc)
wbc_account_cgroup_owner(fio->io_wbc, fio->folio,
--
2.43.0