@@ -1112,14 +1112,12 @@ Exec_ListenPreCommit(void)
1112
1112
amRegisteredListener = true;
1113
1113
1114
1114
/*
1115
- * Try to move our pointer forward as far as possible. This will skip over
1116
- * already-committed notifications. Still, we could get notifications that
1117
- * have already committed before we started to LISTEN.
1118
- *
1119
- * Note that we are not yet listening on anything, so we won't deliver any
1120
- * notification to the frontend. Also, although our transaction might
1121
- * have executed NOTIFY, those message(s) aren't queued yet so we can't
1122
- * see them in the queue.
1115
+ * Try to move our pointer forward as far as possible. This will skip
1116
+ * over already-committed notifications, which we want to do because they
1117
+ * might be quite stale. Note that we are not yet listening on anything,
1118
+ * so we won't deliver such notifications to our frontend. Also, although
1119
+ * our transaction might have executed NOTIFY, those message(s) aren't
1120
+ * queued yet so we won't skip them here.
1123
1121
*/
1124
1122
if (!QUEUE_POS_EQUAL (max , head ))
1125
1123
asyncQueueReadAllNotifications ();
@@ -1938,43 +1936,57 @@ asyncQueueReadAllNotifications(void)
1938
1936
return ;
1939
1937
}
1940
1938
1941
- /* Get snapshot we'll use to decide which xacts are still in progress */
1942
- snapshot = RegisterSnapshot (GetLatestSnapshot ());
1943
-
1944
1939
/*----------
1945
- * Note that we deliver everything that we see in the queue and that
1946
- * matches our _current_ listening state.
1947
- * Especially we do not take into account different commit times.
1940
+ * Get snapshot we'll use to decide which xacts are still in progress.
1941
+ * This is trickier than it might seem, because of race conditions.
1948
1942
* Consider the following example:
1949
1943
*
1950
1944
* Backend 1: Backend 2:
1951
1945
*
1952
1946
* transaction starts
1947
+ * UPDATE foo SET ...;
1953
1948
* NOTIFY foo;
1954
1949
* commit starts
1950
+ * queue the notify message
1955
1951
* transaction starts
1956
- * LISTEN foo;
1957
- * commit starts
1952
+ * LISTEN foo; -- first LISTEN in session
1953
+ * SELECT * FROM foo WHERE ...;
1958
1954
* commit to clog
1955
+ * commit starts
1956
+ * add backend 2 to array of listeners
1957
+ * advance to queue head (this code)
1959
1958
* commit to clog
1960
1959
*
1961
- * It could happen that backend 2 sees the notification from backend 1 in
1962
- * the queue. Even though the notifying transaction committed before
1963
- * the listening transaction, we still deliver the notification.
1960
+ * Transaction 2's SELECT has not seen the UPDATE's effects, since that
1961
+ * wasn't committed yet. Ideally we'd ensure that client 2 would
1962
+ * eventually get transaction 1's notify message, but there's no way
1963
+ * to do that; until we're in the listener array, there's no guarantee
1964
+ * that the notify message doesn't get removed from the queue.
1964
1965
*
1965
- * The idea is that an additional notification does not do any harm, we
1966
- * just need to make sure that we do not miss a notification.
1966
+ * Therefore the coding technique transaction 2 is using is unsafe:
1967
+ * applications must commit a LISTEN before inspecting database state,
1968
+ * if they want to ensure they will see notifications about subsequent
1969
+ * changes to that state.
1967
1970
*
1968
- * It is possible that we fail while trying to send a message to our
1969
- * frontend (for example, because of encoding conversion failure).
1970
- * If that happens it is critical that we not try to send the same
1971
- * message over and over again. Therefore, we place a PG_TRY block
1972
- * here that will forcibly advance our backend position before we lose
1973
- * control to an error. (We could alternatively retake AsyncQueueLock
1974
- * and move the position before handling each individual message, but
1975
- * that seems like too much lock traffic.)
1971
+ * What we do guarantee is that we'll see all notifications from
1972
+ * transactions committing after the snapshot we take here.
1973
+ * Exec_ListenPreCommit has already added us to the listener array,
1974
+ * so no not-yet-committed messages can be removed from the queue
1975
+ * before we see them.
1976
1976
*----------
1977
1977
*/
1978
+ snapshot = RegisterSnapshot (GetLatestSnapshot ());
1979
+
1980
+ /*
1981
+ * It is possible that we fail while trying to send a message to our
1982
+ * frontend (for example, because of encoding conversion failure). If
1983
+ * that happens it is critical that we not try to send the same message
1984
+ * over and over again. Therefore, we place a PG_TRY block here that will
1985
+ * forcibly advance our queue position before we lose control to an error.
1986
+ * (We could alternatively retake AsyncQueueLock and move the position
1987
+ * before handling each individual message, but that seems like too much
1988
+ * lock traffic.)
1989
+ */
1978
1990
PG_TRY ();
1979
1991
{
1980
1992
bool reachedStop ;
0 commit comments