Skip to content

Commit 0e84f70

Browse files
committed
Fix low-probability loss of NOTIFY messages due to XID wraparound.
Up to now async.c has used TransactionIdIsInProgress() to detect whether a notify message's source transaction is still running. However, that function has a quick-exit path that reports that XIDs before RecentXmin are no longer running. If a listening backend is doing nothing but listening, and not running any queries, there is nothing that will advance its value of RecentXmin. Once 2 billion transactions elapse, the RecentXmin check causes active transactions to be reported as not running. If they aren't committed yet according to CLOG, async.c decides they aborted and discards their messages. The timing for that is a bit tight but it can happen when multiple backends are sending notifies concurrently. The net symptom therefore is that a sufficiently-long-surviving listen-only backend starts to miss some fraction of NOTIFY traffic, but only under heavy load. The only function that updates RecentXmin is GetSnapshotData(). A brute-force fix would therefore be to take a snapshot before processing incoming notify messages. But that would add cycles, as well as contention for the ProcArrayLock. We can be smarter: having taken the snapshot, let's use that to check for running XIDs, and not call TransactionIdIsInProgress() at all. In this way we reduce the number of ProcArrayLock acquisitions from one per message to one per notify interrupt; that's the same under light load but should be a benefit under heavy load. Light testing says that this change is a wash performance-wise for normal loads. I looked around for other callers of TransactionIdIsInProgress() that might be at similar risk, and didn't find any; all of them are inside transactions that presumably have already taken a snapshot. Problem report and diagnosis by Marko Tiikkaja, patch by me. Back-patch to all supported branches, since it's been like this since 9.0. Discussion: https://postgr.es/m/20170926182935.14128.65278@wrigleys.postgresql.org
1 parent bfb69b1 commit 0e84f70

File tree

3 files changed

+34
-15
lines changed

3 files changed

+34
-15
lines changed

src/backend/commands/async.c

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,9 @@
133133
#include "utils/builtins.h"
134134
#include "utils/memutils.h"
135135
#include "utils/ps_status.h"
136+
#include "utils/snapmgr.h"
136137
#include "utils/timestamp.h"
138+
#include "utils/tqual.h"
137139

138140

139141
/*
@@ -386,7 +388,8 @@ static bool SignalBackends(void);
386388
static void asyncQueueReadAllNotifications(void);
387389
static bool asyncQueueProcessPageEntries(volatile QueuePosition *current,
388390
QueuePosition stop,
389-
char *page_buffer);
391+
char *page_buffer,
392+
Snapshot snapshot);
390393
static void asyncQueueAdvanceTail(void);
391394
static void ProcessIncomingNotify(void);
392395
static void NotifyMyFrontEnd(const char *channel,
@@ -797,7 +800,7 @@ PreCommit_Notify(void)
797800
}
798801
}
799802

800-
/* Queue any pending notifies */
803+
/* Queue any pending notifies (must happen after the above) */
801804
if (pendingNotifies)
802805
{
803806
ListCell *nextNotify;
@@ -986,7 +989,9 @@ Exec_ListenPreCommit(void)
986989
* have already committed before we started to LISTEN.
987990
*
988991
* Note that we are not yet listening on anything, so we won't deliver any
989-
* notification to the frontend.
992+
* notification to the frontend. Also, although our transaction might
993+
* have executed NOTIFY, those message(s) aren't queued yet so we can't
994+
* see them in the queue.
990995
*
991996
* This will also advance the global tail pointer if possible.
992997
*/
@@ -1835,6 +1840,7 @@ asyncQueueReadAllNotifications(void)
18351840
volatile QueuePosition pos;
18361841
QueuePosition oldpos;
18371842
QueuePosition head;
1843+
Snapshot snapshot;
18381844
bool advanceTail;
18391845

18401846
/* page_buffer must be adequately aligned, so use a union */
@@ -1858,6 +1864,9 @@ asyncQueueReadAllNotifications(void)
18581864
return;
18591865
}
18601866

1867+
/* Get snapshot we'll use to decide which xacts are still in progress */
1868+
snapshot = RegisterSnapshot(GetLatestSnapshot());
1869+
18611870
/*----------
18621871
* Note that we deliver everything that we see in the queue and that
18631872
* matches our _current_ listening state.
@@ -1945,7 +1954,8 @@ asyncQueueReadAllNotifications(void)
19451954
* while sending the notifications to the frontend.
19461955
*/
19471956
reachedStop = asyncQueueProcessPageEntries(&pos, head,
1948-
page_buffer.buf);
1957+
page_buffer.buf,
1958+
snapshot);
19491959
} while (!reachedStop);
19501960
}
19511961
PG_CATCH();
@@ -1973,6 +1983,9 @@ asyncQueueReadAllNotifications(void)
19731983
/* If we were the laziest backend, try to advance the tail pointer */
19741984
if (advanceTail)
19751985
asyncQueueAdvanceTail();
1986+
1987+
/* Done with snapshot */
1988+
UnregisterSnapshot(snapshot);
19761989
}
19771990

19781991
/*
@@ -1994,7 +2007,8 @@ asyncQueueReadAllNotifications(void)
19942007
static bool
19952008
asyncQueueProcessPageEntries(volatile QueuePosition *current,
19962009
QueuePosition stop,
1997-
char *page_buffer)
2010+
char *page_buffer,
2011+
Snapshot snapshot)
19982012
{
19992013
bool reachedStop = false;
20002014
bool reachedEndOfPage;
@@ -2019,7 +2033,7 @@ asyncQueueProcessPageEntries(volatile QueuePosition *current,
20192033
/* Ignore messages destined for other databases */
20202034
if (qe->dboid == MyDatabaseId)
20212035
{
2022-
if (TransactionIdIsInProgress(qe->xid))
2036+
if (XidInMVCCSnapshot(qe->xid, snapshot))
20232037
{
20242038
/*
20252039
* The source transaction is still in progress, so we can't
@@ -2030,10 +2044,15 @@ asyncQueueProcessPageEntries(volatile QueuePosition *current,
20302044
* this advance-then-back-up behavior when dealing with an
20312045
* uncommitted message.)
20322046
*
2033-
* Note that we must test TransactionIdIsInProgress before we
2034-
* test TransactionIdDidCommit, else we might return a message
2035-
* from a transaction that is not yet visible to snapshots;
2036-
* compare the comments at the head of tqual.c.
2047+
* Note that we must test XidInMVCCSnapshot before we test
2048+
* TransactionIdDidCommit, else we might return a message from
2049+
* a transaction that is not yet visible to snapshots; compare
2050+
* the comments at the head of tqual.c.
2051+
*
2052+
* Also, while our own xact won't be listed in the snapshot,
2053+
* we need not check for TransactionIdIsCurrentTransactionId
2054+
* because our transaction cannot (yet) have queued any
2055+
* messages.
20372056
*/
20382057
*current = thisentry;
20392058
reachedStop = true;

src/backend/utils/time/tqual.c

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,6 @@ SnapshotData SnapshotSelfData = {HeapTupleSatisfiesSelf};
7373
SnapshotData SnapshotAnyData = {HeapTupleSatisfiesAny};
7474
SnapshotData SnapshotToastData = {HeapTupleSatisfiesToast};
7575

76-
/* local functions */
77-
static bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot);
7876

7977
/*
8078
* SetHintBits()
@@ -1404,10 +1402,11 @@ HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin)
14041402
*
14051403
* Note: GetSnapshotData never stores either top xid or subxids of our own
14061404
* backend into a snapshot, so these xids will not be reported as "running"
1407-
* by this function. This is OK for current uses, because we actually only
1408-
* apply this for known-committed XIDs.
1405+
* by this function. This is OK for current uses, because we always check
1406+
* TransactionIdIsCurrentTransactionId first, except when it's known the
1407+
* XID could not be ours anyway.
14091408
*/
1410-
static bool
1409+
bool
14111410
XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot)
14121411
{
14131412
uint32 i;

src/include/utils/tqual.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ extern HTSV_Result HeapTupleSatisfiesVacuum(HeapTuple htup,
8585
TransactionId OldestXmin, Buffer buffer);
8686
extern bool HeapTupleIsSurelyDead(HeapTuple htup,
8787
TransactionId OldestXmin);
88+
extern bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot);
8889

8990
extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
9091
uint16 infomask, TransactionId xid);

0 commit comments

Comments
 (0)