]> granicus.if.org Git - postgresql/commitdiff
Fix off-by-one error in calculating subtrans/multixact truncation point.
authorHeikki Linnakangas <heikki.linnakangas@iki.fi>
Wed, 22 Jul 2015 22:30:19 +0000 (01:30 +0300)
committerHeikki Linnakangas <heikki.linnakangas@iki.fi>
Wed, 22 Jul 2015 22:30:19 +0000 (01:30 +0300)
If there were no subtransactions (or multixacts) active, we would calculate
the oldestxid == next xid. That's correct, but if next XID happens to be
on the next pg_subtrans (pg_multixact) page, the page does not exist yet,
and SimpleLruTruncate will produce an "apparent wraparound" warning. The
warning is harmless in this case, but looks very alarming to users.

Backpatch to all supported versions. Patch and analysis by Thomas Munro.

src/backend/access/transam/multixact.c
src/backend/access/transam/subtrans.c
src/include/access/multixact.h

index 5369dbae3241825e76abad96d11801e13f2b6676..a26d508a4d2dc4c3484f7dbd84fb39379b02a15a 100644 (file)
@@ -90,6 +90,8 @@
 #define MXOffsetToMemberEntry(xid) \
        ((xid) % (TransactionId) MULTIXACT_MEMBERS_PER_PAGE)
 
+#define PreviousMultiXactId(xid) \
+       ((xid) == FirstMultiXactId ? MaxMultiXactId : (xid) - 1)
 
 /*
  * Links to shared-memory data structures for MultiXact control
@@ -1902,17 +1904,21 @@ TruncateMultiXact(void)
        }
 
        /*
-        * The cutoff point is the start of the segment containing oldestMXact. We
-        * pass the *page* containing oldestMXact to SimpleLruTruncate.
+        * The cutoff point is the start of the segment containing oldestMXact.
+        * We step back one multixact to avoid passing a cutoff page that hasn't
+        * been created yet in the rare case that oldestMXact would be the first
+        * item on a page and oldestMXact == nextMXact.  In that case, if we
+        * didn't subtract one, we'd trigger SimpleLruTruncate's wraparound
+        * detection.
         */
-       cutoffPage = MultiXactIdToOffsetPage(oldestMXact);
+       cutoffPage = MultiXactIdToOffsetPage(PreviousMultiXactId(oldestMXact));
 
        SimpleLruTruncate(MultiXactOffsetCtl, cutoffPage);
 
        /*
         * Also truncate MultiXactMember at the previously determined offset.
         */
-       cutoffPage = MXOffsetToMemberPage(oldestOffset);
+       cutoffPage = MXOffsetToMemberPage(oldestOffset - 1);
 
        SimpleLruTruncate(MultiXactMemberCtl, cutoffPage);
 
index 05d3f93c9fe11226927351350bcb9370dc1a9489..97511a0c7020e50720540192e1f7ff314eea9d7d 100644 (file)
@@ -340,8 +340,13 @@ TruncateSUBTRANS(TransactionId oldestXact)
 
        /*
         * The cutoff point is the start of the segment containing oldestXact. We
-        * pass the *page* containing oldestXact to SimpleLruTruncate.
+        * pass the *page* containing oldestXact to SimpleLruTruncate.  We step
+        * back one transaction to avoid passing a cutoff page that hasn't been
+        * created yet in the rare case that oldestXact would be the first item on
+        * a page and oldestXact == next XID.  In that case, if we didn't subtract
+        * one, we'd trigger SimpleLruTruncate's wraparound detection.
         */
+       TransactionIdRetreat(oldestXact);
        cutoffPage = TransactionIdToPage(oldestXact);
 
        SimpleLruTruncate(SubTransCtl, cutoffPage);
index 3a21033f535ad96453439a720370eb904d97377f..5f03532936139af5b6f0a6405325ca39ac5c9294 100644 (file)
@@ -15,6 +15,7 @@
 
 #define InvalidMultiXactId     ((MultiXactId) 0)
 #define FirstMultiXactId       ((MultiXactId) 1)
+#define MaxMultiXactId         ((MultiXactId) 0xFFFFFFFF)
 
 #define MultiXactIdIsValid(multi) ((multi) != InvalidMultiXactId)