Fix off-by-one error in calculating subtrans/multixact truncation point.
authorHeikki Linnakangas
Wed, 22 Jul 2015 22:30:09 +0000 (01:30 +0300)
committerHeikki Linnakangas
Wed, 22 Jul 2015 22:30:09 +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

index e655b905238903fd37328754aceb3649669dd01d..fd7b34fe4b590653963adabfe8d275b6526966f0 100644 (file)
 #define MULTIXACT_MEMBER_DANGER_THRESHOLD  \
    (MaxMultiXactOffset - MaxMultiXactOffset / 4)
 
+#define PreviousMultiXactId(xid) \
+   ((xid) == FirstMultiXactId ? MaxMultiXactId : (xid) - 1)
 
 /*
  * Links to shared-memory data structures for MultiXact control
@@ -3076,9 +3078,15 @@ TruncateMultiXact(void)
 
    SlruScanDirectory(MultiXactMemberCtl, SlruScanDirCbRemoveMembers, &range);
 
-   /* Now we can truncate MultiXactOffset */
+   /*
+    * Now we can truncate MultiXactOffset.  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.
+    */
    SimpleLruTruncate(MultiXactOffsetCtl,
-                     MultiXactIdToOffsetPage(oldestMXact));
+                 MultiXactIdToOffsetPage(PreviousMultiXactId(oldestMXact)));
 
    /*
     * Now, and only now, we can advance the stop point for multixact members.
index e3531ea66935513f82451cea6ab7b8fbe06f159b..659c95bce1edb57101d0f8c46fb4d348f7177674 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);