Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitd3aa114

Browse files
committed
Doc: improve discussion of race conditions involved in LISTEN.
The user docs didn't really explain how to use LISTEN safely,so clarify that. Also clean up some fuzzy-headed explanationsin comments. No code changes.Discussion:https://postgr.es/m/3ac7f397-4d5f-be8e-f354-440020675694@gmail.com
1 parent6b802cf commitd3aa114

File tree

2 files changed

+65
-36
lines changed

2 files changed

+65
-36
lines changed

‎doc/src/sgml/ref/listen.sgml

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,6 @@ LISTEN <replaceable class="parameter">channel</replaceable>
6363
<command>LISTEN</command> or <command>UNLISTEN</command> directly. See the
6464
documentation for the interface you are using for more details.
6565
</para>
66-
67-
<para>
68-
<xref linkend="sql-notify"/>
69-
contains a more extensive
70-
discussion of the use of <command>LISTEN</command> and
71-
<command>NOTIFY</command>.
72-
</para>
7366
</refsect1>
7467

7568
<refsect1>
@@ -96,10 +89,34 @@ LISTEN <replaceable class="parameter">channel</replaceable>
9689
within a transaction that later rolls back, the set of notification
9790
channels being listened to is unchanged.
9891
</para>
92+
9993
<para>
10094
A transaction that has executed <command>LISTEN</command> cannot be
10195
prepared for two-phase commit.
10296
</para>
97+
98+
<para>
99+
There is a race condition when first setting up a listening session:
100+
if concurrently-committing transactions are sending notify events,
101+
exactly which of those will the newly listening session receive?
102+
The answer is that the session will receive all events committed after
103+
an instant during the transaction's commit step. But that is slightly
104+
later than any database state that the transaction could have observed
105+
in queries. This leads to the following rule for
106+
using <command>LISTEN</command>: first execute (and commit!) that
107+
command, then in a new transaction inspect the database state as needed
108+
by the application logic, then rely on notifications to find out about
109+
subsequent changes to the database state. The first few received
110+
notifications might refer to updates already observed in the initial
111+
database inspection, but this is usually harmless.
112+
</para>
113+
114+
<para>
115+
<xref linkend="sql-notify"/>
116+
contains a more extensive
117+
discussion of the use of <command>LISTEN</command> and
118+
<command>NOTIFY</command>.
119+
</para>
103120
</refsect1>
104121

105122
<refsect1>

‎src/backend/commands/async.c

Lines changed: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -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
boolreachedStop;

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp