Fix wrong logic in TransactionIdInRecentPast()
authorAlexander Korotkov <akorotkov@postgresql.org>
Thu, 8 Feb 2024 10:45:26 +0000 (12:45 +0200)
committerAlexander Korotkov <akorotkov@postgresql.org>
Thu, 8 Feb 2024 10:45:26 +0000 (12:45 +0200)
The TransactionIdInRecentPast() should return false for all the transactions
older than TransamVariables->oldestClogXid.  However, the function contains
a bug in comparison FullTransactionId to TransactionID allowing full
transactions between nextXid - 2^32 and oldestClogXid - 2^31.

This commit fixes TransactionIdInRecentPast() by turning the oldestClogXid into
FullTransactionId first, then performing the comparison.

Backpatch to all supported versions.

Reported-by: Egor Chindyaskin
Bug: 18212
Discussion: https://postgr.es/m/18212-547307f8adf57262%40postgresql.org
Author: Karina Litskevich
Reviewed-by: Kyotaro Horiguchi
Backpatch-through: 12

src/backend/utils/adt/xid8funcs.c

index be5e28c93ab77940afcc36d30602ae78dd71d291..aa64a7c8b3fc4d1fcd123b762642f6d0509f86ca 100644 (file)
@@ -98,11 +98,12 @@ StaticAssertDecl(MAX_BACKENDS * 2 <= PG_SNAPSHOT_MAX_NXIP,
 static bool
 TransactionIdInRecentPast(FullTransactionId fxid, TransactionId *extracted_xid)
 {
-       uint32          xid_epoch = EpochFromFullTransactionId(fxid);
        TransactionId xid = XidFromFullTransactionId(fxid);
        uint32          now_epoch;
        TransactionId now_epoch_next_xid;
        FullTransactionId now_fullxid;
+       TransactionId oldest_xid;
+       FullTransactionId oldest_fxid;
 
        now_fullxid = ReadNextFullTransactionId();
        now_epoch_next_xid = XidFromFullTransactionId(now_fullxid);
@@ -135,17 +136,24 @@ TransactionIdInRecentPast(FullTransactionId fxid, TransactionId *extracted_xid)
        Assert(LWLockHeldByMe(XactTruncationLock));
 
        /*
-        * If the transaction ID has wrapped around, it's definitely too old to
-        * determine the commit status.  Otherwise, we can compare it to
-        * TransamVariables->oldestClogXid to determine whether the relevant CLOG
-        * entry is guaranteed to still exist.
+        * If fxid is not older than TransamVariables->oldestClogXid, the relevant
+        * CLOG entry is guaranteed to still exist.  Convert
+        * TransamVariables->oldestClogXid into a FullTransactionId to compare it
+        * with fxid.  Determine the right epoch knowing that oldest_fxid
+        * shouldn't be more than 2^31 older than now_fullxid.
         */
-       if (xid_epoch + 1 < now_epoch
-               || (xid_epoch + 1 == now_epoch && xid < now_epoch_next_xid)
-               || TransactionIdPrecedes(xid, TransamVariables->oldestClogXid))
-               return false;
-
-       return true;
+       oldest_xid = TransamVariables->oldestClogXid;
+       Assert(TransactionIdPrecedesOrEquals(oldest_xid, now_epoch_next_xid));
+       if (oldest_xid <= now_epoch_next_xid)
+       {
+               oldest_fxid = FullTransactionIdFromEpochAndXid(now_epoch, oldest_xid);
+       }
+       else
+       {
+               Assert(now_epoch > 0);
+               oldest_fxid = FullTransactionIdFromEpochAndXid(now_epoch - 1, oldest_xid);
+       }
+       return !FullTransactionIdPrecedes(fxid, oldest_fxid);
 }
 
 /*