[PATCH 22/33] xfs: convert xfs_reflink_end_cow to rolling transactions
Dave Chinner <[email protected]> Wed, 29 Jul 2026 20:02:06 +1000
| Newsgroups | org.kernel.vger.linux-xfs |
|---|---|
| Message-ID | <[email protected]> |
Convert xfs_reflink_end_cow() from per-extent transaction allocation to a single rolling transaction that keeps the ILOCK held across the entire COW remapping loop. The rolling transaction keeps the ILOCK held throughout, making the COW remapping operation atomic with respect to other ILOCK-protected extent manipulations such as truncate, reflink remapping, and other concurrent end_cow operations on overlapping regions. XFS_TRANS_RENEW_BLKRES is set so that the btree split block reservation is automatically renewed by xfs_defer_finish() after each iteration's deferred operations are processed. Assisted-by: LLM Signed-off-by: Dave Chinner <[email protected]> --- fs/xfs/xfs_reflink.c | 75 ++++++++++++++++++++++---------------------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/fs/xfs/xfs_reflink.c b/fs/xfs/xfs_reflink.c index b175a549ee55..7e62f05499be 100644 --- a/fs/xfs/xfs_reflink.c +++ b/fs/xfs/xfs_reflink.c @@ -925,6 +925,13 @@ xfs_reflink_end_cow_extent( /* * Remap parts of a file's data fork after a successful CoW. + * + * The rolling transaction keeps the ILOCK held across the entire remapping + * loop, making the operation atomic with respect to other ILOCK-protected + * extent manipulations such as truncate, reflink remapping, and other + * concurrent end_cow operations on overlapping regions. + * XFS_TRANS_RENEW_BLKRES ensures the btree split block reservation is + * renewed after each xfs_defer_finish() call. */ int xfs_reflink_end_cow( @@ -932,54 +939,48 @@ xfs_reflink_end_cow( xfs_off_t offset, xfs_off_t count) { + struct xfs_mount *mp = ip->i_mount; xfs_fileoff_t offset_fsb; xfs_fileoff_t end_fsb; - int error = 0; + struct xfs_trans *tp; + unsigned int resblks; + int error; trace_xfs_reflink_end_cow(ip, offset, count); - offset_fsb = XFS_B_TO_FSBT(ip->i_mount, offset); - end_fsb = XFS_B_TO_FSB(ip->i_mount, offset + count); + offset_fsb = XFS_B_TO_FSBT(mp, offset); + end_fsb = XFS_B_TO_FSB(mp, offset + count); - /* - * Walk forwards until we've remapped the I/O range. The loop function - * repeatedly cycles the ILOCK to allocate one transaction per remapped - * extent. - * - * If we're being called by writeback then the folios will still - * have the writeback flag set, which prevents races with reflink - * remapping and truncate. Reflink remapping prevents races with - * writeback by taking the iolock and mmaplock before flushing - * the folios and remapping, which means there won't be any further - * writeback or page cache dirtying until the reflink completes. - * - * We should never have two threads issuing writeback for the same file - * region. There are also have post-eof checks in the writeback - * preparation code so that we don't bother writing out folios that are - * about to be truncated. - * - * If we're being called as part of directio write completion, the dio - * count is still elevated, which reflink and truncate will wait for. - * Reflink remapping takes the iolock and mmaplock and waits for - * pending dio to finish, which should prevent any directio until the - * remap completes. Multiple concurrent directio writes to the same - * region are handled by end_cow processing only occurring for the - * threads which succeed; the outcome of multiple overlapping direct - * writes is not well defined anyway. - * - * It's possible that a buffered write and a direct write could collide - * here (the buffered write stumbles in after the dio flushes and - * invalidates the page cache and immediately queues writeback), but we - * have never supported this 100%. If either disk write succeeds the - * blocks will be remapped. - */ - while (end_fsb > offset_fsb && !error) - error = xfs_reflink_end_cow_extent(NULL, ip, &offset_fsb, + resblks = XFS_EXTENTADD_SPACE_RES(mp, XFS_DATA_FORK); + error = xfs_trans_alloc(mp, &M_RES(mp)->tr_write, resblks, 0, + XFS_TRANS_RESERVE | XFS_TRANS_RENEW_BLKRES, &tp); + if (error) + return error; + xfs_ilock(ip, XFS_ILOCK_EXCL); + xfs_trans_ijoin(tp, ip, 0); + + while (end_fsb > offset_fsb) { + error = xfs_reflink_end_cow_extent(tp, ip, &offset_fsb, end_fsb); + if (error) + goto out_cancel; + + error = xfs_defer_finish(&tp); + if (error) + goto out_cancel; + } + error = xfs_trans_commit(tp); + xfs_iunlock(ip, XFS_ILOCK_EXCL); if (error) trace_xfs_reflink_end_cow_error(ip, error, _RET_IP_); return error; + +out_cancel: + xfs_trans_cancel(tp); + xfs_iunlock(ip, XFS_ILOCK_EXCL); + trace_xfs_reflink_end_cow_error(ip, error, _RET_IP_); + return error; } /* -- 2.55.0