@@ -1112,14 +1112,12 @@ Exec_ListenPreCommit(void)
11121112 amRegisteredListener = true;
11131113
11141114 /*
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.
11231121 */
11241122 if (!QUEUE_POS_EQUAL (max , head ))
11251123 asyncQueueReadAllNotifications ();
@@ -1938,43 +1936,57 @@ asyncQueueReadAllNotifications(void)
19381936 return ;
19391937 }
19401938
1941- /* Get snapshot we'll use to decide which xacts are still in progress */
1942- snapshot = RegisterSnapshot (GetLatestSnapshot ());
1943-
19441939 /*----------
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.
19481942 * Consider the following example:
19491943 *
19501944 * Backend 1: Backend 2:
19511945 *
19521946 * transaction starts
1947+ * UPDATE foo SET ...;
19531948 * NOTIFY foo;
19541949 * commit starts
1950+ * queue the notify message
19551951 * transaction starts
1956- * LISTEN foo;
1957- * commit starts
1952+ * LISTEN foo; -- first LISTEN in session
1953+ * SELECT * FROM foo WHERE ...;
19581954 * commit to clog
1955+ * commit starts
1956+ * add backend 2 to array of listeners
1957+ * advance to queue head (this code)
19591958 * commit to clog
19601959 *
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.
19641965 *
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.
19671970 *
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.
19761976 *----------
19771977 */
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+ */
19781990 PG_TRY ();
19791991 {
19801992 bool reachedStop ;
0 commit comments