Skip to content

Commit 40ad782

Browse files
committed
Fix off-by-one error in calculating subtrans/multixact truncation point.
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.
1 parent 4c11967 commit 40ad782

File tree

3 files changed

+17
-5
lines changed

3 files changed

+17
-5
lines changed

src/backend/access/transam/multixact.c

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@
9090
#define MXOffsetToMemberEntry(xid) \
9191
((xid) % (TransactionId) MULTIXACT_MEMBERS_PER_PAGE)
9292

93+
#define PreviousMultiXactId(xid) \
94+
((xid) == FirstMultiXactId ? MaxMultiXactId : (xid) - 1)
9395

9496
/*
9597
* Links to shared-memory data structures for MultiXact control
@@ -1902,17 +1904,21 @@ TruncateMultiXact(void)
19021904
}
19031905

19041906
/*
1905-
* The cutoff point is the start of the segment containing oldestMXact. We
1906-
* pass the *page* containing oldestMXact to SimpleLruTruncate.
1907+
* The cutoff point is the start of the segment containing oldestMXact.
1908+
* We step back one multixact to avoid passing a cutoff page that hasn't
1909+
* been created yet in the rare case that oldestMXact would be the first
1910+
* item on a page and oldestMXact == nextMXact. In that case, if we
1911+
* didn't subtract one, we'd trigger SimpleLruTruncate's wraparound
1912+
* detection.
19071913
*/
1908-
cutoffPage = MultiXactIdToOffsetPage(oldestMXact);
1914+
cutoffPage = MultiXactIdToOffsetPage(PreviousMultiXactId(oldestMXact));
19091915

19101916
SimpleLruTruncate(MultiXactOffsetCtl, cutoffPage);
19111917

19121918
/*
19131919
* Also truncate MultiXactMember at the previously determined offset.
19141920
*/
1915-
cutoffPage = MXOffsetToMemberPage(oldestOffset);
1921+
cutoffPage = MXOffsetToMemberPage(oldestOffset - 1);
19161922

19171923
SimpleLruTruncate(MultiXactMemberCtl, cutoffPage);
19181924

src/backend/access/transam/subtrans.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,8 +340,13 @@ TruncateSUBTRANS(TransactionId oldestXact)
340340

341341
/*
342342
* The cutoff point is the start of the segment containing oldestXact. We
343-
* pass the *page* containing oldestXact to SimpleLruTruncate.
343+
* pass the *page* containing oldestXact to SimpleLruTruncate. We step
344+
* back one transaction to avoid passing a cutoff page that hasn't been
345+
* created yet in the rare case that oldestXact would be the first item on
346+
* a page and oldestXact == next XID. In that case, if we didn't subtract
347+
* one, we'd trigger SimpleLruTruncate's wraparound detection.
344348
*/
349+
TransactionIdRetreat(oldestXact);
345350
cutoffPage = TransactionIdToPage(oldestXact);
346351

347352
SimpleLruTruncate(SubTransCtl, cutoffPage);

src/include/access/multixact.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
#define InvalidMultiXactId ((MultiXactId) 0)
1717
#define FirstMultiXactId ((MultiXactId) 1)
18+
#define MaxMultiXactId ((MultiXactId) 0xFFFFFFFF)
1819

1920
#define MultiXactIdIsValid(multi) ((multi) != InvalidMultiXactId)
2021

0 commit comments

Comments
 (0)