From: Dave Chinner <dgc@kernel.org>
To: linux-xfs@vger.kernel.org
Cc: cem@kernel.org
Subject: [PATCH 23/38] xfs: add block reservation renewal to xfs_defer_finish
Date: Wed, 19 Aug 2026 10:12:26 +1000 [thread overview]
Message-ID: <20260819001442.1451892-24-dgc@kernel.org> (raw)
In-Reply-To: <20260819001442.1451892-1-dgc@kernel.org>
Add infrastructure to automatically renew block and RT extent
reservations after deferred operations have been processed by
xfs_defer_finish().
Unlike log reservations which are renewed by xfs_log_regrant() on
every roll, block reservations are carried forward by
xfs_trans_dup() with consumed blocks subtracted. Over multiple
rolls, the reservation can be depleted even though each iteration
of the caller needs the same reservation.
Add a new XFS_TRANS_RENEW_BLKRES flag. When set, the original
block reservation is recorded in t_blk_res_orig (and t_rtx_res_orig
for RT extents) at reservation time and propagated through
xfs_trans_dup(). After xfs_defer_finish() completes all deferred
operations and performs the final roll, xfs_trans_regrant_blkres()
reserves the deficit between t_blk_res and t_blk_res_orig from the
free space pool, restoring the original reservation for the next
iteration.
The _orig values are set in xfs_trans_reserve() and
xfs_trans_reserve_more_inode() so that they are correctly captured
regardless of how the block reservation was established.
The regrant is not performed during the internal rolls in
xfs_defer_finish_noroll() because the original reservation already
contains all the space needed for the deferred op chain. The regrant
after the final roll operates on a clean transaction, so the caller
can safely cancel on ENOSPC without causing a filesystem shutdown.
xfs_defer_finish() is changed to unconditionally roll the transaction
after deferred op processing so that the regrant always occurs.
Assisted-by: LLM
Signed-off-by: Dave Chinner <dgc@kernel.org>
---
fs/xfs/libxfs/xfs_defer.c | 38 +++++++++++++++++++++++++-----------
fs/xfs/libxfs/xfs_shared.h | 3 +++
fs/xfs/xfs_trans.c | 40 +++++++++++++++++++++++++++++++++++---
fs/xfs/xfs_trans.h | 3 +++
4 files changed, 70 insertions(+), 14 deletions(-)
diff --git a/fs/xfs/libxfs/xfs_defer.c b/fs/xfs/libxfs/xfs_defer.c
index 89501e8bd2f8..f1807dadafb2 100644
--- a/fs/xfs/libxfs/xfs_defer.c
+++ b/fs/xfs/libxfs/xfs_defer.c
@@ -725,6 +725,24 @@ xfs_defer_finish_noroll(
return error;
}
+/*
+ * Finish all deferred ops and roll the transaction. The transaction is
+ * always rolled unconditionally so that the block reservation can be
+ * regranted after all deferred ops have completed.
+ *
+ * The block reservation regrant is not performed during the internal
+ * transaction rolls in xfs_defer_finish_noroll() because the original
+ * reservation already contains all the space needed for the deferred op
+ * chain. Regranting during internal rolls would risk unnecessary ENOSPC
+ * errors in the middle of a deferred op chain that cannot be safely
+ * aborted.
+ *
+ * The regrant is done after the final roll when the transaction is clean,
+ * replenishing whatever blocks were consumed by both the caller's
+ * modifications and the deferred operations. If the regrant fails with
+ * ENOSPC, the caller can safely cancel the clean transaction without
+ * causing a filesystem shutdown.
+ */
int
xfs_defer_finish(
struct xfs_trans **tp)
@@ -734,22 +752,20 @@ xfs_defer_finish(
#endif
int error;
- /*
- * Finish and roll the transaction once more to avoid returning to the
- * caller with a dirty transaction.
- */
error = xfs_defer_finish_noroll(tp);
if (error)
return error;
- if ((*tp)->t_flags & XFS_TRANS_DIRTY) {
- error = xfs_defer_trans_roll(tp);
- if (error) {
- xfs_force_shutdown((*tp)->t_mountp,
- SHUTDOWN_CORRUPT_INCORE);
- return error;
- }
+
+ error = xfs_defer_trans_roll(tp);
+ if (error) {
+ xfs_force_shutdown((*tp)->t_mountp, SHUTDOWN_CORRUPT_INCORE);
+ return error;
}
+ error = xfs_trans_regrant_blkres(*tp);
+ if (error)
+ return error;
+
/* Reset LOWMODE now that we've finished all the dfops. */
#ifdef DEBUG
list_for_each_entry(dfp, &(*tp)->t_dfops, dfp_list)
diff --git a/fs/xfs/libxfs/xfs_shared.h b/fs/xfs/libxfs/xfs_shared.h
index b1e0d9bc1f7d..e3909d46f3cd 100644
--- a/fs/xfs/libxfs/xfs_shared.h
+++ b/fs/xfs/libxfs/xfs_shared.h
@@ -164,6 +164,9 @@ void xfs_log_get_max_trans_res(struct xfs_mount *mp,
/* Transaction has locked the rtbitmap and rtsum inodes */
#define XFS_TRANS_RTBITMAP_LOCKED (1u << 9)
+/* Renew block reservation on transaction roll */
+#define XFS_TRANS_RENEW_BLKRES (1u << 10)
+
/*
* Field values for xfs_trans_mod_sb.
*/
diff --git a/fs/xfs/xfs_trans.c b/fs/xfs/xfs_trans.c
index 8e32da47501e..6a274e3aed36 100644
--- a/fs/xfs/xfs_trans.c
+++ b/fs/xfs/xfs_trans.c
@@ -112,16 +112,19 @@ xfs_trans_dup(
ntp->t_flags = XFS_TRANS_PERM_LOG_RES |
(tp->t_flags & XFS_TRANS_RESERVE) |
(tp->t_flags & XFS_TRANS_NO_WRITECOUNT) |
- (tp->t_flags & XFS_TRANS_RES_FDBLKS);
+ (tp->t_flags & XFS_TRANS_RES_FDBLKS) |
+ (tp->t_flags & XFS_TRANS_RENEW_BLKRES);
/* We gave our writer reference to the new transaction */
tp->t_flags |= XFS_TRANS_NO_WRITECOUNT;
ntp->t_ticket = xfs_log_ticket_get(tp->t_ticket);
ASSERT(tp->t_blk_res >= tp->t_blk_res_used);
ntp->t_blk_res = tp->t_blk_res - tp->t_blk_res_used;
+ ntp->t_blk_res_orig = tp->t_blk_res_orig;
tp->t_blk_res = tp->t_blk_res_used;
ntp->t_rtx_res = tp->t_rtx_res - tp->t_rtx_res_used;
+ ntp->t_rtx_res_orig = tp->t_rtx_res_orig;
tp->t_rtx_res = tp->t_rtx_res_used;
/* move deferred ops over to the new tp */
@@ -203,6 +206,8 @@ xfs_trans_reserve(
error = xfs_trans_reserve_blocks(tp, blocks, rtextents);
if (error)
return error;
+ tp->t_blk_res_orig = tp->t_blk_res;
+ tp->t_rtx_res_orig = tp->t_rtx_res;
/*
* Reserve the log space needed for this transaction.
@@ -1009,6 +1014,31 @@ xfs_trans_cancel(
xfs_trans_free(tp);
}
+/*
+ * Renew the block and RT extent reservations from the free space pool.
+ * The consumed counts were carried forward by xfs_trans_dup() so we know
+ * exactly how many blocks need to be reserved to restore the original
+ * reservation.
+ */
+int
+xfs_trans_regrant_blkres(
+ struct xfs_trans *tp)
+{
+ unsigned int blk_deficit;
+ unsigned int rtx_deficit;
+
+ if (!(tp->t_flags & XFS_TRANS_RENEW_BLKRES))
+ return 0;
+
+ blk_deficit = tp->t_blk_res_orig - tp->t_blk_res;
+ rtx_deficit = tp->t_rtx_res_orig - tp->t_rtx_res;
+
+ if (!blk_deficit && !rtx_deficit)
+ return 0;
+
+ return xfs_trans_reserve_blocks(tp, blk_deficit, rtx_deficit);
+}
+
/*
* Roll from one trans in the sequence of PERMANENT transactions to the next:
* permanent transactions are only flushed out when committed with
@@ -1181,12 +1211,12 @@ xfs_trans_reserve_more_inode(
if (!XFS_IS_QUOTA_ON(mp) ||
xfs_is_quota_inode(&mp->m_sb, I_INO(ip)))
- return 0;
+ break;
error = xfs_trans_reserve_quota_nblks(tp, ip, dblocks,
rblocks, force_quota);
if (!error)
- return 0;
+ break;
xfs_trans_unreserve_blocks(tp, dblocks, rtx);
@@ -1197,6 +1227,10 @@ xfs_trans_reserve_more_inode(
return error;
} while (retry++ == 0);
+ if (!error) {
+ tp->t_blk_res_orig = tp->t_blk_res;
+ tp->t_rtx_res_orig = tp->t_rtx_res;
+ }
return error;
}
diff --git a/fs/xfs/xfs_trans.h b/fs/xfs/xfs_trans.h
index de77b617bccf..cf527d57d40b 100644
--- a/fs/xfs/xfs_trans.h
+++ b/fs/xfs/xfs_trans.h
@@ -127,8 +127,10 @@ typedef struct xfs_trans {
unsigned int t_log_count; /* count for perm log res */
unsigned int t_blk_res; /* # of blocks resvd */
unsigned int t_blk_res_used; /* # of resvd blocks used */
+ unsigned int t_blk_res_orig; /* used with XFS_TRANS_RENEW_BLKRES */
unsigned int t_rtx_res; /* # of rt extents resvd */
unsigned int t_rtx_res_used; /* # of resvd rt extents used */
+ unsigned int t_rtx_res_orig; /* used with XFS_TRANS_RENEW_BLKRES */
unsigned int t_flags; /* misc flags */
xfs_agnumber_t t_highest_agno; /* highest AGF locked */
struct xlog_ticket *t_ticket; /* log mgr ticket */
@@ -239,6 +241,7 @@ void xfs_trans_log_inode(xfs_trans_t *, struct xfs_inode *, uint);
int xfs_trans_commit(struct xfs_trans *);
int xfs_trans_roll(struct xfs_trans **);
int xfs_trans_roll_inode(struct xfs_trans **, struct xfs_inode *);
+int xfs_trans_regrant_blkres(struct xfs_trans *);
void xfs_trans_cancel(xfs_trans_t *);
int xfs_trans_ail_init(struct xfs_mount *);
void xfs_trans_ail_destroy(struct xfs_mount *);
--
2.55.0
next prev parent reply other threads:[~2026-08-19 0:15 UTC|newest]
Thread overview: 39+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-19 0:12 [PATCH V2 00/38] XFS: Atomic multi-extent operations via rolling transactions Dave Chinner
2026-08-19 0:12 ` [PATCH 01/38] xfs: fix dirty transaction cancellation in xfs_bmapi_convert_one_delalloc Dave Chinner
2026-08-19 0:12 ` [PATCH 02/38] xfs: fix dirty transaction cancellation in xfs_attr_set Dave Chinner
2026-08-19 0:12 ` [PATCH 03/38] xfs: fix isize update in xfs_iomap_write_unwritten to track conversion progress Dave Chinner
2026-08-19 0:12 ` [PATCH 04/38] xfs: fix block reservation for zoned RT extent remapping Dave Chinner
2026-08-19 0:12 ` [PATCH 05/38] xfs: factor xfs_trans_reserve_blocks() from xfs_trans_reserve() Dave Chinner
2026-08-19 0:12 ` [PATCH 06/38] xfs: factor xfs_blockgc_start_flush() from xfs_blockgc_flush_all() Dave Chinner
2026-08-19 0:12 ` [PATCH 07/38] xfs: add async quota-targeted blockgc flush Dave Chinner
2026-08-19 0:12 ` [PATCH 08/38] xfs: add async blockgc retry to xfs_trans_reserve_more_inode() Dave Chinner
2026-08-19 0:12 ` [PATCH 09/38] xfs: factor out COW iomap handling from xfs_direct_write_iomap_begin() Dave Chinner
2026-08-19 0:12 ` [PATCH 10/38] xfs: plumb xfs_trans through xfs_reflink_allocate_cow and fill_cow_hole Dave Chinner
2026-08-19 0:12 ` [PATCH 11/38] xfs: teach xfs_reflink_fill_cow_hole() to use a caller-supplied transaction Dave Chinner
2026-08-19 0:12 ` [PATCH 12/38] xfs: add transaction retry infrastructure to xfs_direct_write_cow_iomap_begin Dave Chinner
2026-08-19 0:12 ` [PATCH 13/38] xfs: return -EAGAIN from xfs_reflink_allocate_cow for COW hole without transaction Dave Chinner
2026-08-19 0:12 ` [PATCH 14/38] xfs: remove internal transaction allocation from xfs_reflink_fill_cow_hole Dave Chinner
2026-08-19 0:12 ` [PATCH 15/38] xfs: use zero-block transaction with xfs_trans_reserve_more_inode for COW holes Dave Chinner
2026-08-19 0:12 ` [PATCH 16/38] xfs: change *tp to **tpp in COW allocation call chain Dave Chinner
2026-08-19 0:12 ` [PATCH 17/38] xfs: convert xfs_reflink_fill_delalloc to use rolling transactions Dave Chinner
2026-08-19 0:12 ` [PATCH 18/38] xfs: return -EAGAIN from xfs_reflink_allocate_cow for all allocation cases Dave Chinner
2026-08-19 0:12 ` [PATCH 19/38] xfs: remove dead internal transaction allocation from xfs_reflink_fill_delalloc Dave Chinner
2026-08-19 0:12 ` [PATCH 20/38] xfs: plumb struct xfs_trans *tp into xfs_bmapi_convert_one_delalloc Dave Chinner
2026-08-19 0:12 ` [PATCH 21/38] xfs: use rolling transaction in xfs_bmapi_convert_delalloc Dave Chinner
2026-08-19 0:12 ` [PATCH 22/38] xfs: remove dead internal transaction path from xfs_bmapi_convert_one_delalloc Dave Chinner
2026-08-19 0:12 ` Dave Chinner [this message]
2026-08-19 0:12 ` [PATCH 24/38] xfs: factor out xfs_iomap_write_unwritten_one helper Dave Chinner
2026-08-19 0:12 ` [PATCH 25/38] xfs: convert xfs_iomap_write_unwritten to rolling transactions Dave Chinner
2026-08-19 0:12 ` [PATCH 26/38] xfs: plumb struct xfs_trans *tp into xfs_reflink_end_cow_extent Dave Chinner
2026-08-19 0:12 ` [PATCH 27/38] xfs: convert xfs_reflink_end_cow to rolling transactions Dave Chinner
2026-08-19 0:12 ` [PATCH 28/38] xfs: remove xfs_reflink_end_cow_extent wrapper and rename locked variant Dave Chinner
2026-08-19 0:12 ` [PATCH 29/38] xfs: convert xfs_zoned_end_io to rolling transactions Dave Chinner
2026-08-19 0:12 ` [PATCH 30/38] xfs: plumb struct xfs_trans *tp into xfs_iomap_write_direct Dave Chinner
2026-08-19 0:12 ` [PATCH 31/38] xfs: make xfs_iomap_write_direct fill in the iomap directly Dave Chinner
2026-08-19 0:12 ` [PATCH 32/38] xfs: plumb struct xfs_trans **tpp into xfs_direct_write_cow_iomap_begin Dave Chinner
2026-08-19 0:12 ` [PATCH 33/38] xfs: introduce struct xfs_direct_write_args for direct write call chain Dave Chinner
2026-08-19 0:12 ` [PATCH 34/38] xfs: convert xfs_direct_write_iomap_begin to use dwa struct throughout Dave Chinner
2026-08-19 0:12 ` [PATCH 35/38] xfs: restructure xfs_direct_write_iomap_begin with unified retry loop Dave Chinner
2026-08-19 0:12 ` [PATCH 36/38] xfs: clean up xfs_direct_write_cow_iomap_begin after restructure Dave Chinner
2026-08-19 0:12 ` [PATCH 37/38] xfs: make pNFS block allocation atomic with inode update Dave Chinner
2026-08-19 0:12 ` [PATCH 38/38] 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=20260819001442.1451892-24-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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox