Skip to content

Commit 8eace46

Browse files
committed
Fix race condition in reading commit timestamps
If a user requests the commit timestamp for a transaction old enough that its data is concurrently being truncated away by vacuum at just the right time, they would receive an ugly internal file-not-found error message from slru.c rather than the expected NULL return value. In a primary server, the window for the race is very small: the lookup has to occur exactly between the two calls by vacuum, and there's not a lot that happens between them (mostly just a multixact truncate). In a standby server, however, the window is larger because the truncation is executed as soon as the WAL record for it is replayed, but the advance of the oldest-Xid is not executed until the next checkpoint record. To fix in the primary, simply reverse the order of operations in vac_truncate_clog. To fix in the standby, augment the WAL truncation record so that the standby is aware of the new oldest-XID value and can apply the update immediately. WAL version bumped because of this. No backpatch, because of the low importance of the bug and its rarity. Author: Craig Ringer Reviewed-By: Petr Jelínek, Peter Eisentraut Discussion: https://postgr.es/m/CAMsr+YFhVtRQT1VAwC+WGbbxZZRzNou=N9Ed-FrCqkwQ8H8oJQ@mail.gmail.com
1 parent 8b0fec9 commit 8eace46

File tree

5 files changed

+34
-13
lines changed

5 files changed

+34
-13
lines changed

src/backend/access/rmgrdesc/committsdesc.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ commit_ts_desc(StringInfo buf, XLogReaderState *record)
3333
}
3434
else if (info == COMMIT_TS_TRUNCATE)
3535
{
36-
int pageno;
36+
xl_commit_ts_truncate *trunc = (xl_commit_ts_truncate *) rec;
3737

38-
memcpy(&pageno, rec, sizeof(int));
39-
appendStringInfo(buf, "%d", pageno);
38+
appendStringInfo(buf, "pageno %d, oldestXid %u",
39+
trunc->pageno, trunc->oldestXid);
4040
}
4141
else if (info == COMMIT_TS_SETTS)
4242
{

src/backend/access/transam/commit_ts.c

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ static bool CommitTsPagePrecedes(int page1, int page2);
113113
static void ActivateCommitTs(void);
114114
static void DeactivateCommitTs(void);
115115
static void WriteZeroPageXlogRec(int pageno);
116-
static void WriteTruncateXlogRec(int pageno);
116+
static void WriteTruncateXlogRec(int pageno, TransactionId oldestXid);
117117
static void WriteSetTimestampXlogRec(TransactionId mainxid, int nsubxids,
118118
TransactionId *subxids, TimestampTz timestamp,
119119
RepOriginId nodeid);
@@ -824,7 +824,7 @@ TruncateCommitTs(TransactionId oldestXact)
824824
return; /* nothing to remove */
825825

826826
/* Write XLOG record */
827-
WriteTruncateXlogRec(cutoffPage);
827+
WriteTruncateXlogRec(cutoffPage, oldestXact);
828828

829829
/* Now we can remove the old CommitTs segment(s) */
830830
SimpleLruTruncate(CommitTsCtl, cutoffPage);
@@ -910,10 +910,15 @@ WriteZeroPageXlogRec(int pageno)
910910
* Write a TRUNCATE xlog record
911911
*/
912912
static void
913-
WriteTruncateXlogRec(int pageno)
913+
WriteTruncateXlogRec(int pageno, TransactionId oldestXid)
914914
{
915+
xl_commit_ts_truncate xlrec;
916+
917+
xlrec.pageno = pageno;
918+
xlrec.oldestXid = oldestXid;
919+
915920
XLogBeginInsert();
916-
XLogRegisterData((char *) (&pageno), sizeof(int));
921+
XLogRegisterData((char *) (&xlrec), SizeOfCommitTsTruncate);
917922
(void) XLogInsert(RM_COMMIT_TS_ID, COMMIT_TS_TRUNCATE);
918923
}
919924

@@ -967,17 +972,17 @@ commit_ts_redo(XLogReaderState *record)
967972
}
968973
else if (info == COMMIT_TS_TRUNCATE)
969974
{
970-
int pageno;
975+
xl_commit_ts_truncate *trunc = (xl_commit_ts_truncate *) XLogRecGetData(record);
971976

972-
memcpy(&pageno, XLogRecGetData(record), sizeof(int));
977+
AdvanceOldestCommitTsXid(trunc->oldestXid);
973978

974979
/*
975980
* During XLOG replay, latest_page_number isn't set up yet; insert a
976981
* suitable value to bypass the sanity test in SimpleLruTruncate.
977982
*/
978-
CommitTsCtl->shared->latest_page_number = pageno;
983+
CommitTsCtl->shared->latest_page_number = trunc->pageno;
979984

980-
SimpleLruTruncate(CommitTsCtl, pageno);
985+
SimpleLruTruncate(CommitTsCtl, trunc->pageno);
981986
}
982987
else if (info == COMMIT_TS_SETTS)
983988
{

src/backend/commands/vacuum.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1152,6 +1152,15 @@ vac_truncate_clog(TransactionId frozenXID,
11521152
if (bogus)
11531153
return;
11541154

1155+
/*
1156+
* Advance the oldest value for commit timestamps before truncating, so
1157+
* that if a user requests a timestamp for a transaction we're truncating
1158+
* away right after this point, they get NULL instead of an ugly "file not
1159+
* found" error from slru.c. This doesn't matter for xact/multixact
1160+
* because they are not subject to arbitrary lookups from users.
1161+
*/
1162+
AdvanceOldestCommitTsXid(frozenXID);
1163+
11551164
/*
11561165
* Truncate CLOG, multixact and CommitTs to the oldest computed value.
11571166
*/
@@ -1167,7 +1176,6 @@ vac_truncate_clog(TransactionId frozenXID,
11671176
*/
11681177
SetTransactionIdLimit(frozenXID, oldestxid_datoid);
11691178
SetMultiXactIdLimit(minMulti, minmulti_datoid);
1170-
AdvanceOldestCommitTsXid(frozenXID);
11711179
}
11721180

11731181

src/include/access/commit_ts.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,14 @@ typedef struct xl_commit_ts_set
6161
#define SizeOfCommitTsSet (offsetof(xl_commit_ts_set, mainxid) + \
6262
sizeof(TransactionId))
6363

64+
typedef struct xl_commit_ts_truncate
65+
{
66+
int pageno;
67+
TransactionId oldestXid;
68+
} xl_commit_ts_truncate;
69+
70+
#define SizeOfCommitTsTruncate (offsetof(xl_commit_ts_truncate, oldestXid) + \
71+
sizeof(TransactionId))
6472

6573
extern void commit_ts_redo(XLogReaderState *record);
6674
extern void commit_ts_desc(StringInfo buf, XLogReaderState *record);

src/include/access/xlog_internal.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
/*
3232
* Each page of XLOG file has a header like this:
3333
*/
34-
#define XLOG_PAGE_MAGIC 0xD093 /* can be used as WAL version indicator */
34+
#define XLOG_PAGE_MAGIC 0xD094 /* can be used as WAL version indicator */
3535

3636
typedef struct XLogPageHeaderData
3737
{

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy