From: Dave Chinner <dgc@kernel.org>
To: linux-xfs@vger.kernel.org
Cc: cem@kernel.org
Subject: [PATCH 30/33] xfs: restructure xfs_direct_write_iomap_begin with unified retry loop
Date: Wed, 29 Jul 2026 20:02:14 +1000 [thread overview]
Message-ID: <20260729100629.1943710-31-dgc@kernel.org> (raw)
In-Reply-To: <20260729100629.1943710-1-dgc@kernel.org>
Replace the split COW/non-COW structure in xfs_direct_write_iomap_begin()
with a unified do {} while (dwa.tp) loop that handles both COW extent
allocation and data fork allocation atomically under a single ILOCK
hold.
The loop structure:
- xfs_ilock_for_iomap() is called once before the loop
- xfs_bmapi_read() at the top of each iteration reads fresh state
- COW check calls xfs_direct_write_cow_iomap_begin() with
needs_tp=false, which returns -EAGAIN if a transaction is needed
- If allocation is needed (needs_alloc) but no transaction, falls
through to alloc_trans
- If a transaction is available, calls xfs_iomap_write_direct()
with the transaction, commits, and returns
- Overwrite path cancels unused tp and returns the mapping
- alloc_trans at the tail: ASSERT(!tp), checks NOWAIT/OVERWRITE,
drops the ILOCK, allocates a zero-block transaction via
xfs_trans_alloc_inode() (which reacquires ILOCK_EXCL), and
loops back to re-read the extent tree
This makes the extent lookup and allocation atomic with respect to
the ILOCK for both COW and non-COW paths.
Assisted-by: LLM
Signed-off-by: Dave Chinner <dgc@kernel.org>
---
fs/xfs/xfs_iomap.c | 218 ++++++++++++++++++++++++++++-----------------
1 file changed, 135 insertions(+), 83 deletions(-)
diff --git a/fs/xfs/xfs_iomap.c b/fs/xfs/xfs_iomap.c
index b388ded20ffc..d58ecc05a4d1 100644
--- a/fs/xfs/xfs_iomap.c
+++ b/fs/xfs/xfs_iomap.c
@@ -1077,110 +1077,162 @@ xfs_direct_write_iomap_begin(
if (flags & IOMAP_ATOMIC)
dwa.iomap_flags |= IOMAP_F_ATOMIC_BIO;
- if (xfs_is_cow_inode(ip)) {
- error = xfs_direct_write_cow_iomap_begin(&dwa, true);
- if (error)
- return error;
- if (!dwa.nimaps)
- return 0;
-
- end_fsb = dwa.imap.br_startoff + dwa.imap.br_blockcount;
- dwa.length = XFS_FSB_TO_B(mp, end_fsb) - offset;
- } else {
+ if (xfs_is_cow_inode(ip))
+ dwa.lockmode = XFS_ILOCK_EXCL;
+ else
dwa.lockmode = XFS_ILOCK_SHARED;
- error = xfs_ilock_for_iomap(ip, flags, &dwa.lockmode);
- if (error)
- return error;
+ error = xfs_ilock_for_iomap(ip, flags, &dwa.lockmode);
+ if (error)
+ return error;
+ do {
+ dwa.nimaps = 1;
error = xfs_bmapi_read(ip, dwa.offset_fsb,
end_fsb - dwa.offset_fsb,
&dwa.imap, &dwa.nimaps, 0);
if (error)
goto out_unlock;
- }
- needs_alloc = imap_needs_alloc(inode, flags, &dwa.imap, dwa.nimaps);
-
- if (flags & IOMAP_ATOMIC) {
- error = -ENOPROTOOPT;
- /*
- * If we allocate less than what is required for the write
- * then we may end up with multiple extents, which means that
- * REQ_ATOMIC-based cannot be used, so avoid this possibility.
- */
- if (needs_alloc && orig_end_fsb - dwa.offset_fsb > 1)
- goto out_unlock;
+ if (xfs_is_cow_inode(ip)) {
+ error = xfs_direct_write_cow_iomap_begin(&dwa, false);
+ if (error == -EAGAIN)
+ goto alloc_trans;
+ if (error)
+ goto out_unlock;
+ if (!dwa.nimaps) {
+ /*
+ * COW iomaps filled in. Commit the
+ * transaction if one was needed and
+ * return.
+ */
+ if (dwa.tp)
+ error = xfs_trans_commit(dwa.tp);
+ xfs_iunlock(ip, dwa.lockmode);
+ return error;
+ }
- if (!xfs_bmap_hw_atomic_write_possible(ip, &dwa.imap,
- dwa.offset_fsb, orig_end_fsb))
- goto out_unlock;
- }
+ end_fsb = dwa.imap.br_startoff +
+ dwa.imap.br_blockcount;
+ dwa.length = XFS_FSB_TO_B(mp, end_fsb) - offset;
+ }
- if (needs_alloc)
- goto allocate_blocks;
+ needs_alloc = imap_needs_alloc(inode, flags, &dwa.imap,
+ dwa.nimaps);
+
+ if (flags & IOMAP_ATOMIC) {
+ error = -ENOPROTOOPT;
+ /*
+ * If we allocate less than what is required for the
+ * write then we may end up with multiple extents,
+ * which means that REQ_ATOMIC-based cannot be used,
+ * so avoid this possibility.
+ */
+ if (needs_alloc &&
+ orig_end_fsb - dwa.offset_fsb > 1)
+ goto out_unlock;
+ if (!xfs_bmap_hw_atomic_write_possible(ip, &dwa.imap,
+ dwa.offset_fsb, orig_end_fsb))
+ goto out_unlock;
+ }
- /*
- * NOWAIT and OVERWRITE I/O needs to span the entire requested I/O with
- * a single map so that we avoid partial IO failures due to the rest of
- * the I/O range not covered by this map triggering an EAGAIN condition
- * when it is subsequently mapped and aborting the I/O.
- */
- if (flags & (IOMAP_NOWAIT | IOMAP_OVERWRITE_ONLY)) {
- error = -EAGAIN;
- if (!imap_spans_range(&dwa.imap, dwa.offset_fsb, end_fsb))
- goto out_unlock;
- }
+ if (needs_alloc) {
+ if (!dwa.tp)
+ goto alloc_trans;
+
+ /*
+ * Cap the maximum length we map to a sane size to
+ * keep the chunks of work done where somewhat
+ * symmetric with the work writeback does.
+ *
+ * Note that the values needs to be less than
+ * 32-bits wide until the lower level functions
+ * are updated.
+ */
+ dwa.length = min_t(loff_t, dwa.length,
+ 1024 * PAGE_SIZE);
+ end_fsb = xfs_iomap_end_fsb(mp, offset, dwa.length);
+
+ if (offset + dwa.length > XFS_ISIZE(ip))
+ end_fsb = xfs_iomap_eof_align_last_fsb(ip,
+ end_fsb);
+ else if (dwa.nimaps &&
+ dwa.imap.br_startblock == HOLESTARTBLOCK)
+ end_fsb = min(end_fsb, dwa.imap.br_startoff +
+ dwa.imap.br_blockcount);
+
+ dwa.count_fsb = end_fsb - dwa.offset_fsb;
+
+ error = xfs_iomap_write_direct(&dwa);
+ if (error)
+ goto out_unlock;
- /*
- * For overwrite only I/O, we cannot convert unwritten extents without
- * requiring sub-block zeroing. This can only be done under an
- * exclusive IOLOCK, hence return -EAGAIN if this is not a written
- * extent to tell the caller to try again.
- */
- if (flags & IOMAP_OVERWRITE_ONLY) {
- error = -EAGAIN;
- if (dwa.imap.br_state != XFS_EXT_NORM &&
- ((offset | dwa.length) & mp->m_blockmask))
- goto out_unlock;
- }
+ error = xfs_trans_commit(dwa.tp);
+ xfs_iunlock(ip, dwa.lockmode);
+ return error;
+ }
- seq = xfs_iomap_inode_sequence(ip, dwa.iomap_flags);
- xfs_iunlock(ip, dwa.lockmode);
- trace_xfs_iomap_found(ip, offset, dwa.length, XFS_DATA_FORK,
- &dwa.imap);
- return xfs_bmbt_to_iomap(ip, iomap, &dwa.imap, flags,
- dwa.iomap_flags, seq);
+ /*
+ * Overwrite path - no allocation needed. Cancel unused
+ * transaction if allocated and return the mapping.
+ */
+ if (dwa.tp) {
+ xfs_trans_cancel(dwa.tp);
+ dwa.tp = NULL;
+ }
-allocate_blocks:
- error = -EAGAIN;
- if (flags & (IOMAP_NOWAIT | IOMAP_OVERWRITE_ONLY))
- goto out_unlock;
+ /*
+ * NOWAIT and OVERWRITE I/O needs to span the entire
+ * requested I/O with a single map so that we avoid
+ * partial IO failures due to the rest of the I/O range
+ * not covered by this map triggering an EAGAIN condition
+ * when it is subsequently mapped and aborting the I/O.
+ */
+ if (flags & (IOMAP_NOWAIT | IOMAP_OVERWRITE_ONLY)) {
+ error = -EAGAIN;
+ if (!imap_spans_range(&dwa.imap, dwa.offset_fsb,
+ end_fsb))
+ goto out_unlock;
+ }
+ /*
+ * For overwrite only I/O, we cannot convert unwritten
+ * extents without requiring sub-block zeroing. This
+ * can only be done under an exclusive IOLOCK, hence
+ * return -EAGAIN if this is not a written extent to
+ * tell the caller to try again.
+ */
+ if (flags & IOMAP_OVERWRITE_ONLY) {
+ error = -EAGAIN;
+ if (dwa.imap.br_state != XFS_EXT_NORM &&
+ ((offset | dwa.length) & mp->m_blockmask))
+ goto out_unlock;
+ }
- /*
- * We cap the maximum length we map to a sane size to keep the chunks
- * of work done where somewhat symmetric with the work writeback does.
- * This is a completely arbitrary number pulled out of thin air as a
- * best guess for initial testing.
- *
- * Note that the values needs to be less than 32-bits wide until the
- * lower level functions are updated.
- */
- dwa.length = min_t(loff_t, dwa.length, 1024 * PAGE_SIZE);
- end_fsb = xfs_iomap_end_fsb(mp, offset, dwa.length);
+ seq = xfs_iomap_inode_sequence(ip, dwa.iomap_flags);
+ xfs_iunlock(ip, dwa.lockmode);
+ trace_xfs_iomap_found(ip, offset, dwa.length, XFS_DATA_FORK,
+ &dwa.imap);
+ return xfs_bmbt_to_iomap(ip, iomap, &dwa.imap, flags,
+ dwa.iomap_flags, seq);
- if (offset + dwa.length > XFS_ISIZE(ip))
- end_fsb = xfs_iomap_eof_align_last_fsb(ip, end_fsb);
- else if (dwa.nimaps && dwa.imap.br_startblock == HOLESTARTBLOCK)
- end_fsb = min(end_fsb, dwa.imap.br_startoff +
- dwa.imap.br_blockcount);
+alloc_trans:
+ ASSERT(!dwa.tp);
- dwa.count_fsb = end_fsb - dwa.offset_fsb;
- xfs_iunlock(ip, dwa.lockmode);
+ error = -EAGAIN;
+ if (flags & (IOMAP_NOWAIT | IOMAP_OVERWRITE_ONLY))
+ goto out_unlock;
- return xfs_iomap_write_direct(&dwa);
+ xfs_iunlock(ip, dwa.lockmode);
+ error = xfs_trans_alloc_inode(ip, &M_RES(mp)->tr_write,
+ 0, 0, false, &dwa.tp);
+ if (error)
+ return error;
+ dwa.lockmode = XFS_ILOCK_EXCL;
+ } while (dwa.tp);
out_unlock:
+ if (dwa.tp)
+ xfs_trans_cancel(dwa.tp);
if (dwa.lockmode)
xfs_iunlock(ip, dwa.lockmode);
return error;
--
2.55.0
next prev parent reply other threads:[~2026-07-29 10:07 UTC|newest]
Thread overview: 34+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-29 10:01 [RFC PATCH 00/33] XFS: Atomic multi-extent operations via rolling transactions Dave Chinner
2026-07-29 10:01 ` [PATCH 01/33] xfs: fix dirty transaction cancellation in xfs_bmapi_convert_one_delalloc Dave Chinner
2026-07-29 10:01 ` [PATCH 02/33] xfs: fix isize update in xfs_iomap_write_unwritten to track conversion progress Dave Chinner
2026-07-29 10:01 ` [PATCH 03/33] xfs: fix block reservation for zoned RT extent remapping Dave Chinner
2026-07-29 10:01 ` [PATCH 04/33] xfs: factor out COW iomap handling from xfs_direct_write_iomap_begin() Dave Chinner
2026-07-29 10:01 ` [PATCH 05/33] xfs: plumb xfs_trans through xfs_reflink_allocate_cow and fill_cow_hole Dave Chinner
2026-07-29 10:01 ` [PATCH 06/33] xfs: teach xfs_reflink_fill_cow_hole() to use a caller-supplied transaction Dave Chinner
2026-07-29 10:01 ` [PATCH 07/33] xfs: add transaction retry infrastructure to xfs_direct_write_cow_iomap_begin Dave Chinner
2026-07-29 10:01 ` [PATCH 08/33] xfs: return -EAGAIN from xfs_reflink_allocate_cow for COW hole without transaction Dave Chinner
2026-07-29 10:01 ` [PATCH 09/33] xfs: remove internal transaction allocation from xfs_reflink_fill_cow_hole Dave Chinner
2026-07-29 10:01 ` [PATCH 10/33] xfs: use zero-block transaction with xfs_trans_reserve_more_inode for COW holes Dave Chinner
2026-07-29 10:01 ` [PATCH 11/33] xfs: change *tp to **tpp in COW allocation call chain Dave Chinner
2026-07-29 10:01 ` [PATCH 12/33] xfs: convert xfs_reflink_fill_delalloc to use rolling transactions Dave Chinner
2026-07-29 10:01 ` [PATCH 13/33] xfs: return -EAGAIN from xfs_reflink_allocate_cow for all allocation cases Dave Chinner
2026-07-29 10:01 ` [PATCH 14/33] xfs: remove dead internal transaction allocation from xfs_reflink_fill_delalloc Dave Chinner
2026-07-29 10:01 ` [PATCH 15/33] xfs: plumb struct xfs_trans *tp into xfs_bmapi_convert_one_delalloc Dave Chinner
2026-07-29 10:02 ` [PATCH 16/33] xfs: use rolling transaction in xfs_bmapi_convert_delalloc Dave Chinner
2026-07-29 10:02 ` [PATCH 17/33] xfs: remove dead internal transaction path from xfs_bmapi_convert_one_delalloc Dave Chinner
2026-07-29 10:02 ` [PATCH 18/33] xfs: add block reservation renewal to xfs_defer_finish Dave Chinner
2026-07-29 10:02 ` [PATCH 19/33] xfs: factor out xfs_iomap_write_unwritten_one helper Dave Chinner
2026-07-29 10:02 ` [PATCH 20/33] xfs: convert xfs_iomap_write_unwritten to rolling transactions Dave Chinner
2026-07-29 10:02 ` [PATCH 21/33] xfs: plumb struct xfs_trans *tp into xfs_reflink_end_cow_extent Dave Chinner
2026-07-29 10:02 ` [PATCH 22/33] xfs: convert xfs_reflink_end_cow to rolling transactions Dave Chinner
2026-07-29 10:02 ` [PATCH 23/33] xfs: remove xfs_reflink_end_cow_extent wrapper and rename locked variant Dave Chinner
2026-07-29 10:02 ` [PATCH 24/33] xfs: convert xfs_zoned_end_io to rolling transactions Dave Chinner
2026-07-29 10:02 ` [PATCH 25/33] xfs: plumb struct xfs_trans *tp into xfs_iomap_write_direct Dave Chinner
2026-07-29 10:02 ` [PATCH 26/33] xfs: make xfs_iomap_write_direct fill in the iomap directly Dave Chinner
2026-07-29 10:02 ` [PATCH 27/33] xfs: plumb struct xfs_trans **tpp into xfs_direct_write_cow_iomap_begin Dave Chinner
2026-07-29 10:02 ` [PATCH 28/33] xfs: introduce struct xfs_direct_write_args for direct write call chain Dave Chinner
2026-07-29 10:02 ` [PATCH 29/33] xfs: convert xfs_direct_write_iomap_begin to use dwa struct throughout Dave Chinner
2026-07-29 10:02 ` Dave Chinner [this message]
2026-07-29 10:02 ` [PATCH 31/33] xfs: clean up xfs_direct_write_cow_iomap_begin after restructure Dave Chinner
2026-07-29 10:02 ` [PATCH 32/33] xfs: make pNFS block allocation atomic with inode update Dave Chinner
2026-07-29 10:02 ` [PATCH 33/33] xfs: remove dead internal transaction path from xfs_iomap_write_direct Dave Chinner
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=20260729100629.1943710-31-dgc@kernel.org \
--to=dgc@kernel.org \
--cc=cem@kernel.org \
--cc=linux-xfs@vger.kernel.org \
/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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.