]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
xfs: only run precommits once per transaction object
authorDarrick J. Wong <djwong@kernel.org>
Mon, 2 Dec 2024 18:57:33 +0000 (10:57 -0800)
committerDarrick J. Wong <djwong@kernel.org>
Fri, 13 Dec 2024 01:45:10 +0000 (17:45 -0800)
Committing a transaction tx0 with a defer ops chain of (A, B, C)
creates a chain of transactions that looks like this:

tx0 -> txA -> txB -> txC

Prior to commit cb042117488dbf, __xfs_trans_commit would run precommits
on tx0, then call xfs_defer_finish_noroll to convert A-C to tx[A-C].
Unfortunately, after the finish_noroll loop we forgot to run precommits
on txC.  That was fixed by adding the second precommit call.

Unfortunately, none of us remembered that xfs_defer_finish_noroll
calls __xfs_trans_commit a second time to commit tx0 before finishing
work A in txA and committing that.  In other words, we run precommits
twice on tx0:

xfs_trans_commit(tx0)
    __xfs_trans_commit(tx0, false)
        xfs_trans_run_precommits(tx0)
        xfs_defer_finish_noroll(tx0)
            xfs_trans_roll(tx0)
                txA = xfs_trans_dup(tx0)
                __xfs_trans_commit(tx0, true)
                xfs_trans_run_precommits(tx0)

This currently isn't an issue because the inode item precommit is
idempotent; the iunlink item precommit deletes itself so it can't be
called again; and the buffer/dquot item precommits only check the incore
objects for corruption.  However, it doesn't make sense to run
precommits twice.

Fix this situation by only running precommits after finish_noroll.

Cc: <stable@vger.kernel.org> # v6.4
Fixes: cb042117488dbf ("xfs: defered work could create precommits")
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
fs/xfs/xfs_trans.c

index 05b18e30368e4bbd57c0d5b9bbe1606cb8fb33fa..4a517250efc911d4ed974895cbb66f4c5b71414d 100644 (file)
@@ -860,13 +860,6 @@ __xfs_trans_commit(
 
        trace_xfs_trans_commit(tp, _RET_IP_);
 
-       error = xfs_trans_run_precommits(tp);
-       if (error) {
-               if (tp->t_flags & XFS_TRANS_PERM_LOG_RES)
-                       xfs_defer_cancel(tp);
-               goto out_unreserve;
-       }
-
        /*
         * Finish deferred items on final commit. Only permanent transactions
         * should ever have deferred ops.
@@ -877,13 +870,12 @@ __xfs_trans_commit(
                error = xfs_defer_finish_noroll(&tp);
                if (error)
                        goto out_unreserve;
-
-               /* Run precommits from final tx in defer chain. */
-               error = xfs_trans_run_precommits(tp);
-               if (error)
-                       goto out_unreserve;
        }
 
+       error = xfs_trans_run_precommits(tp);
+       if (error)
+               goto out_unreserve;
+
        /*
         * If there is nothing to be logged by the transaction,
         * then unlock all of the items associated with the