[RFC PATCH v3 06/11] iomap: Add aio support to RWF_WRITETHROUGH

From: Ojaswin Mujoo

Date: Wed Aug 05 2026 - 02:30:43 EST


With aio the only thing we need to be careful of is that writethrough
can be in progress even after dropping inode and folio lock. Due to
this, we need a way to synchronise with other paths where stable write
is not enough, example:

1. Truncate to 0 in xfs sets i_size = 0 before waiting for writeback to
complete. In case of writethrough, the end io completion can again
push the i_size to a non-zero value.
2. Dio reads might race with aio writethrough ->end_io() and read 0s if
unwritten conversion is yet to happen.

Hence use the dio begin/end as it gives us the required guarantees.

Co-developed-by: Ritesh Harjani (IBM) <ritesh.list@xxxxxxxxx>
Signed-off-by: Ritesh Harjani (IBM) <ritesh.list@xxxxxxxxx>
Signed-off-by: Ojaswin Mujoo <ojaswin@xxxxxxxxxxxxx>
---
fs/iomap/buffered-io.c | 54 ++++++++++++++++++++++++++++++++++++------
include/linux/iomap.h | 11 +++++++--
2 files changed, 56 insertions(+), 9 deletions(-)

diff --git a/fs/iomap/buffered-io.c b/fs/iomap/buffered-io.c
index 3178e8c0fa13..22e4252dff4d 100644
--- a/fs/iomap/buffered-io.c
+++ b/fs/iomap/buffered-io.c
@@ -1202,6 +1202,9 @@ static ssize_t iomap_writethrough_complete(struct iomap_writethrough_ctx *wt_ctx

mapping_dec_inflight_stable_writes(inode->i_mapping);

+ if (wt_ctx->is_aio)
+ inode_dio_end(inode);
+
if (!ret) {
ret = wt_ctx->written;
iocb->ki_pos += ret;
@@ -1211,12 +1214,27 @@ static ssize_t iomap_writethrough_complete(struct iomap_writethrough_ctx *wt_ctx
return ret;
}

+static void iomap_writethrough_complete_work(struct work_struct *work)
+{
+ struct iomap_writethrough_ctx *wt_ctx =
+ container_of(work, struct iomap_writethrough_ctx, aio_work);
+ struct kiocb *iocb = wt_ctx->iocb;
+
+ iocb->ki_complete(iocb, iomap_writethrough_complete(wt_ctx));
+}
+
static void iomap_writethrough_done(struct iomap_writethrough_ctx *wt_ctx)
{
- struct task_struct *waiter = wt_ctx->waiter;
+ if (!wt_ctx->is_aio) {
+ struct task_struct *waiter = wt_ctx->waiter;

- WRITE_ONCE(wt_ctx->waiter, NULL);
- blk_wake_io_task(waiter);
+ WRITE_ONCE(wt_ctx->waiter, NULL);
+ blk_wake_io_task(waiter);
+ return;
+ }
+
+ INIT_WORK(&wt_ctx->aio_work, iomap_writethrough_complete_work);
+ queue_work(wt_ctx->inode->i_sb->s_dio_done_wq, &wt_ctx->aio_work);
}

static void iomap_writethrough_bio_end_io(struct bio *bio)
@@ -1729,9 +1747,6 @@ ssize_t iomap_file_writethrough_write(struct kiocb *iocb, struct iov_iter *i,
if (iocb_is_dsync(iocb))
/* D_SYNC support not implemented yet */
return -EOPNOTSUPP;
- if (!is_sync_kiocb(iocb))
- /* aio support not implemented yet */
- return -EOPNOTSUPP;

/*
* +1 to max bvecs to account for unaligned write spanning multiple
@@ -1750,11 +1765,33 @@ ssize_t iomap_file_writethrough_write(struct kiocb *iocb, struct iov_iter *i,
wt_ctx->end_io = wt_ops->end_io;
wt_ctx->old_i_size = i_size_read(inode);
wt_ctx->max_bvecs = max_bvecs;
+ wt_ctx->is_aio = !is_sync_kiocb(iocb);
atomic_set(&wt_ctx->ref, 1);
- wt_ctx->waiter = current;
+
+ if (!wt_ctx->is_aio)
+ wt_ctx->waiter = current;
+ else
+ /*
+ * With aio, writethrough can be in progress even after dropping
+ * inode and folio lock. Due to this, we need a way to
+ * synchronise with other paths where stable write is not enough
+ * (example truncate). Hence use the dio begin/end as it gives
+ * us the required guarantees.
+ */
+ inode_dio_begin(inode);

mapping_inc_inflight_stable_writes(inode->i_mapping);

+ if (wt_ctx->is_aio && !inode->i_sb->s_dio_done_wq) {
+ ret = sb_init_dio_done_wq(inode->i_sb);
+ if (ret < 0) {
+ mapping_dec_inflight_stable_writes(inode->i_mapping);
+ inode_dio_end(inode);
+ kfree(wt_ctx);
+ return ret;
+ }
+ }
+
blk_start_plug(&plug);

while ((ret = iomap_iter(&iter, wt_ops->ops)) > 0) {
@@ -1769,6 +1806,9 @@ ssize_t iomap_file_writethrough_write(struct kiocb *iocb, struct iov_iter *i,
cmpxchg(&wt_ctx->error, 0, ret);

if (!atomic_dec_and_test(&wt_ctx->ref)) {
+ if (wt_ctx->is_aio)
+ return -EIOCBQUEUED;
+
for (;;) {
set_current_state(TASK_UNINTERRUPTIBLE);
if (!READ_ONCE(wt_ctx->waiter))
diff --git a/include/linux/iomap.h b/include/linux/iomap.h
index 427a2763221c..7203c4d92170 100644
--- a/include/linux/iomap.h
+++ b/include/linux/iomap.h
@@ -572,9 +572,16 @@ struct iomap_writethrough_ctx {
atomic_t ref;
unsigned int flags;
int error;
+ bool is_aio;
+
+ union {
+ /* used during submission and for non-aio completion */
+ struct task_struct *waiter;
+
+ /* used during aio completion */
+ struct work_struct aio_work;
+ };

- /* used during submission and for non-aio completion */
- struct task_struct *waiter;
int (*end_io)(struct iomap_writethrough_ctx *wt_ctx, ssize_t size,
int error, unsigned int flags);

--
2.55.0