Skip to content

Commit ae1525f

Browse files
committed
Fix bogus when-to-deregister-from-listener-array logic.
Since a backend adds itself to the global listener array during Exec_ListenPreCommit, it's inappropriate for it to remove itself during Exec_UnlistenCommit or Exec_UnlistenAllCommit --- that leads to failure when committing a transaction that did UNLISTEN then LISTEN, since we end up not registered though we should be. (This leads to missing later notifications, or to Assert failures in assert-enabled builds.) Instead deal with deregistering at the bottom of AtCommit_Notify, when we know the final state of the listenChannels list. Also, simplify the representation of registration status by replacing the transient backendHasExecutedInitialListen flag with an amRegisteredListener flag. Per report from Greg Sabino Mullane. Back-patch to 9.0, where the problem was introduced during the LISTEN/NOTIFY rewrite.
1 parent 639f5ad commit ae1525f

File tree

1 file changed

+22
-38
lines changed

1 file changed

+22
-38
lines changed

src/backend/commands/async.c

Lines changed: 22 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -349,12 +349,12 @@ static volatile sig_atomic_t notifyInterruptOccurred = 0;
349349
/* True if we've registered an on_shmem_exit cleanup */
350350
static bool unlistenExitRegistered = false;
351351

352+
/* True if we're currently registered as a listener in asyncQueueControl */
353+
static bool amRegisteredListener = false;
354+
352355
/* has this backend sent notifications in the current transaction? */
353356
static bool backendHasSentNotifications = false;
354357

355-
/* has this backend executed its first LISTEN in the current transaction? */
356-
static bool backendHasExecutedInitialListen = false;
357-
358358
/* GUC parameter */
359359
bool Trace_notify = false;
360360

@@ -745,6 +745,7 @@ static void
745745
Async_UnlistenOnExit(int code, Datum arg)
746746
{
747747
Exec_UnlistenAllCommit();
748+
asyncQueueUnregister();
748749
}
749750

750751
/*
@@ -789,8 +790,6 @@ PreCommit_Notify(void)
789790
if (Trace_notify)
790791
elog(DEBUG1, "PreCommit_Notify");
791792

792-
Assert(backendHasExecutedInitialListen == false);
793-
794793
/* Preflight for any pending listen/unlisten actions */
795794
foreach(p, pendingActions)
796795
{
@@ -913,11 +912,9 @@ AtCommit_Notify(void)
913912
}
914913
}
915914

916-
/*
917-
* If we did an initial LISTEN, listenChannels now has the entry, so we no
918-
* longer need or want the flag to be set.
919-
*/
920-
backendHasExecutedInitialListen = false;
915+
/* If no longer listening to anything, get out of listener array */
916+
if (amRegisteredListener && listenChannels == NIL)
917+
asyncQueueUnregister();
921918

922919
/* And clean up */
923920
ClearPendingActionsAndNotifies();
@@ -935,19 +932,12 @@ Exec_ListenPreCommit(void)
935932
* Nothing to do if we are already listening to something, nor if we
936933
* already ran this routine in this transaction.
937934
*/
938-
if (listenChannels != NIL || backendHasExecutedInitialListen)
935+
if (amRegisteredListener)
939936
return;
940937

941938
if (Trace_notify)
942939
elog(DEBUG1, "Exec_ListenPreCommit(%d)", MyProcPid);
943940

944-
/*
945-
* We need this variable to detect an aborted initial LISTEN. In that case
946-
* we would set up our pointer but not listen on any channel. This flag
947-
* gets cleared in AtCommit_Notify or AtAbort_Notify().
948-
*/
949-
backendHasExecutedInitialListen = true;
950-
951941
/*
952942
* Before registering, make sure we will unlisten before dying. (Note:
953943
* this action does not get undone if we abort later.)
@@ -971,6 +961,9 @@ Exec_ListenPreCommit(void)
971961
QUEUE_BACKEND_PID(MyBackendId) = MyProcPid;
972962
LWLockRelease(AsyncQueueLock);
973963

964+
/* Now we are listed in the global array, so remember we're listening */
965+
amRegisteredListener = true;
966+
974967
/*
975968
* Try to move our pointer forward as far as possible. This will skip over
976969
* already-committed notifications. Still, we could get notifications that
@@ -1043,10 +1036,6 @@ Exec_UnlistenCommit(const char *channel)
10431036
* We do not complain about unlistening something not being listened;
10441037
* should we?
10451038
*/
1046-
1047-
/* If no longer listening to anything, get out of listener array */
1048-
if (listenChannels == NIL)
1049-
asyncQueueUnregister();
10501039
}
10511040

10521041
/*
@@ -1062,8 +1051,6 @@ Exec_UnlistenAllCommit(void)
10621051

10631052
list_free_deep(listenChannels);
10641053
listenChannels = NIL;
1065-
1066-
asyncQueueUnregister();
10671054
}
10681055

10691056
/*
@@ -1181,6 +1168,9 @@ asyncQueueUnregister(void)
11811168

11821169
Assert(listenChannels == NIL); /* else caller error */
11831170

1171+
if (!amRegisteredListener) /* nothing to do */
1172+
return;
1173+
11841174
LWLockAcquire(AsyncQueueLock, LW_SHARED);
11851175
/* check if entry is valid and oldest ... */
11861176
advanceTail = (MyProcPid == QUEUE_BACKEND_PID(MyBackendId)) &&
@@ -1189,6 +1179,9 @@ asyncQueueUnregister(void)
11891179
QUEUE_BACKEND_PID(MyBackendId) = InvalidPid;
11901180
LWLockRelease(AsyncQueueLock);
11911181

1182+
/* mark ourselves as no longer listed in the global array */
1183+
amRegisteredListener = false;
1184+
11921185
/* If we were the laziest backend, try to advance the tail pointer */
11931186
if (advanceTail)
11941187
asyncQueueAdvanceTail();
@@ -1545,21 +1538,12 @@ void
15451538
AtAbort_Notify(void)
15461539
{
15471540
/*
1548-
* If we LISTEN but then roll back the transaction we have set our pointer
1549-
* but have not made any entry in listenChannels. In that case, remove our
1550-
* pointer again.
1541+
* If we LISTEN but then roll back the transaction after PreCommit_Notify,
1542+
* we have registered as a listener but have not made any entry in
1543+
* listenChannels. In that case, deregister again.
15511544
*/
1552-
if (backendHasExecutedInitialListen)
1553-
{
1554-
/*
1555-
* Checking listenChannels should be redundant but it can't hurt doing
1556-
* it for safety reasons.
1557-
*/
1558-
if (listenChannels == NIL)
1559-
asyncQueueUnregister();
1560-
1561-
backendHasExecutedInitialListen = false;
1562-
}
1545+
if (amRegisteredListener && listenChannels == NIL)
1546+
asyncQueueUnregister();
15631547

15641548
/* And clean up */
15651549
ClearPendingActionsAndNotifies();

0 commit comments

Comments
 (0)