From: Dave Chinner <dgc@kernel.org>
To: linux-xfs@vger.kernel.org
Cc: cem@kernel.org
Subject: [PATCH 18/33] xfs: add block reservation renewal to xfs_defer_finish
Date: Wed, 29 Jul 2026 20:02:02 +1000 [thread overview]
Message-ID: <20260729100629.1943710-19-dgc@kernel.org> (raw)
In-Reply-To: <20260729100629.1943710-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 transaction allocation 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 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, 72 insertions(+), 12 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 7bfbd9f6f0df..c267a174cbe1 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 */
@@ -164,6 +167,7 @@ xfs_trans_reserve(
if (error != 0)
return -ENOSPC;
tp->t_blk_res += blocks;
+ tp->t_blk_res_orig = tp->t_blk_res;
}
/*
@@ -191,6 +195,7 @@ xfs_trans_reserve(
goto undo_log;
}
tp->t_rtx_res += rtextents;
+ tp->t_rtx_res_orig = tp->t_rtx_res;
}
return 0;
@@ -995,6 +1000,39 @@ 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)
+{
+ int error;
+
+ if (!(tp->t_flags & XFS_TRANS_RENEW_BLKRES))
+ return 0;
+
+ if (tp->t_blk_res != tp->t_blk_res_orig) {
+ error = xfs_dec_fdblocks(tp->t_mountp,
+ tp->t_blk_res_orig - tp->t_blk_res,
+ tp->t_flags & XFS_TRANS_RESERVE);
+ if (error)
+ return error;
+ tp->t_blk_res = tp->t_blk_res_orig;
+ }
+ if (tp->t_rtx_res != tp->t_rtx_res_orig) {
+ error = xfs_dec_frextents(tp->t_mountp,
+ tp->t_rtx_res_orig - tp->t_rtx_res);
+ if (error)
+ return error;
+ tp->t_rtx_res = tp->t_rtx_res_orig;
+ }
+ return 0;
+}
+
/*
* Roll from one trans in the sequence of PERMANENT transactions to the next:
* permanent transactions are only flushed out when committed with
diff --git a/fs/xfs/xfs_trans.h b/fs/xfs/xfs_trans.h
index eb83c5dac032..3d1ddc2a90f7 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 */
@@ -237,6 +239,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-07-29 10:06 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 ` Dave Chinner [this message]
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 ` [PATCH 30/33] xfs: restructure xfs_direct_write_iomap_begin with unified retry loop Dave Chinner
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-19-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.