Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 964f42a

Browse files
committed
Force immediate commit after CREATE DATABASE etc in extended protocol.
We have a few commands that "can't run in a transaction block", meaning that if they complete their processing but then we fail to COMMIT, we'll be left with inconsistent on-disk state. However, the existing defenses for this are only watertight for simple query protocol. In extended protocol, we didn't commit until receiving a Sync message. Since the client is allowed to issue another command instead of Sync, we're in trouble if that command fails or is an explicit ROLLBACK. In any case, sitting in an inconsistent state while waiting for a client message that might not come seems pretty risky. This case wasn't reachable via libpq before we introduced pipeline mode, but it's always been an intended aspect of extended query protocol, and likely there are other clients that could reach it before. To fix, set a flag in PreventInTransactionBlock that tells exec_execute_message to force an immediate commit. This seems to be the approach that does least damage to existing working cases while still preventing the undesirable outcomes. While here, add some documentation to protocol.sgml that explicitly says how to use pipelining. That's latent in the existing docs if you know what to look for, but it's better to spell it out; and it provides a place to document this new behavior. Per bug #17434 from Yugo Nagata. It's been wrong for ages, so back-patch to all supported branches. Discussion: https://postgr.es/m/[email protected]
1 parent 7fa2bde commit 964f42a

File tree

4 files changed

+104
-26
lines changed

4 files changed

+104
-26
lines changed

doc/src/sgml/protocol.sgml

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -931,6 +931,64 @@
931931
</note>
932932
</sect2>
933933

934+
<sect2 id="protocol-flow-pipelining">
935+
<title>Pipelining</title>
936+
937+
<indexterm zone="protocol-flow-pipelining">
938+
<primary>pipelining</primary>
939+
<secondary>protocol specification</secondary>
940+
</indexterm>
941+
942+
<para>
943+
Use of the extended query protocol
944+
allows <firstterm>pipelining</firstterm>, which means sending a series
945+
of queries without waiting for earlier ones to complete. This reduces
946+
the number of network round trips needed to complete a given series of
947+
operations. However, the user must carefully consider the required
948+
behavior if one of the steps fails, since later queries will already
949+
be in flight to the server.
950+
</para>
951+
952+
<para>
953+
One way to deal with that is to make the whole query series be a
954+
single transaction, that is wrap it in <command>BEGIN</command> ...
955+
<command>COMMIT</command>. However, this does not help if one wishes
956+
for some of the commands to commit independently of others.
957+
</para>
958+
959+
<para>
960+
The extended query protocol provides another way to manage this
961+
concern, which is to omit sending Sync messages between steps that
962+
are dependent. Since, after an error, the backend will skip command
963+
messages until it finds Sync, this allows later commands in a pipeline
964+
to be skipped automatically when an earlier one fails, without the
965+
client having to manage that explicitly with <command>BEGIN</command>
966+
and <command>COMMIT</command>. Independently-committable segments
967+
of the pipeline can be separated by Sync messages.
968+
</para>
969+
970+
<para>
971+
If the client has not issued an explicit <command>BEGIN</command>,
972+
then each Sync ordinarily causes an implicit <command>COMMIT</command>
973+
if the preceding step(s) succeeded, or an
974+
implicit <command>ROLLBACK</command> if they failed. However, there
975+
are a few DDL commands (such as <command>CREATE DATABASE</command>)
976+
that cannot be executed inside a transaction block. If one of
977+
these is executed in a pipeline, it will, upon success, force an
978+
immediate commit to preserve database consistency.
979+
A Sync immediately following one of these has no effect except to
980+
respond with ReadyForQuery.
981+
</para>
982+
983+
<para>
984+
When using this method, completion of the pipeline must be determined
985+
by counting ReadyForQuery messages and waiting for that to reach the
986+
number of Syncs sent. Counting command completion responses is
987+
unreliable, since some of the commands may not be executed and thus not
988+
produce a completion message.
989+
</para>
990+
</sect2>
991+
934992
<sect2>
935993
<title>Function Call</title>
936994

src/backend/access/transam/xact.c

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3208,6 +3208,9 @@ AbortCurrentTransaction(void)
32083208
* could issue more commands and possibly cause a failure after the statement
32093209
* completes). Subtransactions are verboten too.
32103210
*
3211+
* We must also set XACT_FLAGS_NEEDIMMEDIATECOMMIT in MyXactFlags, to ensure
3212+
* that postgres.c follows through by committing after the statement is done.
3213+
*
32113214
* isTopLevel: passed down from ProcessUtility to determine whether we are
32123215
* inside a function or multi-query querystring. (We will always fail if
32133216
* this is false, but it's convenient to centralize the check here instead of
@@ -3251,7 +3254,9 @@ PreventTransactionChain(bool isTopLevel, const char *stmtType)
32513254
if (CurrentTransactionState->blockState != TBLOCK_DEFAULT &&
32523255
CurrentTransactionState->blockState != TBLOCK_STARTED)
32533256
elog(FATAL, "cannot prevent transaction chain");
3254-
/* all okay */
3257+
3258+
/* All okay. Set the flag to make sure the right thing happens later. */
3259+
MyXactFlags |= XACT_FLAGS_NEEDIMMEDIATECOMMIT;
32553260
}
32563261

32573262
/*
@@ -3350,6 +3355,13 @@ IsInTransactionChain(bool isTopLevel)
33503355
CurrentTransactionState->blockState != TBLOCK_STARTED)
33513356
return true;
33523357

3358+
/*
3359+
* If we tell the caller we're not in a transaction block, then inform
3360+
* postgres.c that it had better commit when the statement is done.
3361+
* Otherwise our report could be a lie.
3362+
*/
3363+
MyXactFlags |= XACT_FLAGS_NEEDIMMEDIATECOMMIT;
3364+
33533365
return false;
33543366
}
33553367

src/backend/tcop/postgres.c

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1155,6 +1155,13 @@ exec_simple_query(const char *query_string)
11551155
}
11561156
else
11571157
{
1158+
/*
1159+
* We had better not see XACT_FLAGS_NEEDIMMEDIATECOMMIT set if
1160+
* we're not calling finish_xact_command(). (The implicit
1161+
* transaction block should have prevented it from getting set.)
1162+
*/
1163+
Assert(!(MyXactFlags & XACT_FLAGS_NEEDIMMEDIATECOMMIT));
1164+
11581165
/*
11591166
* We need a CommandCounterIncrement after every query, except
11601167
* those that start or end a transaction block.
@@ -1903,32 +1910,16 @@ exec_execute_message(const char *portal_name, long max_rows)
19031910

19041911
/*
19051912
* We must copy the sourceText and prepStmtName into MessageContext in
1906-
* case the portal is destroyed during finish_xact_command. Can avoid the
1907-
* copy if it's not an xact command, though.
1913+
* case the portal is destroyed during finish_xact_command. We do not
1914+
* make a copy of the portalParams though, preferring to just not print
1915+
* them in that case.
19081916
*/
1909-
if (is_xact_command)
1910-
{
1911-
sourceText = pstrdup(portal->sourceText);
1912-
if (portal->prepStmtName)
1913-
prepStmtName = pstrdup(portal->prepStmtName);
1914-
else
1915-
prepStmtName = "<unnamed>";
1916-
1917-
/*
1918-
* An xact command shouldn't have any parameters, which is a good
1919-
* thing because they wouldn't be around after finish_xact_command.
1920-
*/
1921-
portalParams = NULL;
1922-
}
1917+
sourceText = pstrdup(portal->sourceText);
1918+
if (portal->prepStmtName)
1919+
prepStmtName = pstrdup(portal->prepStmtName);
19231920
else
1924-
{
1925-
sourceText = portal->sourceText;
1926-
if (portal->prepStmtName)
1927-
prepStmtName = portal->prepStmtName;
1928-
else
1929-
prepStmtName = "<unnamed>";
1930-
portalParams = portal->portalParams;
1931-
}
1921+
prepStmtName = "<unnamed>";
1922+
portalParams = portal->portalParams;
19321923

19331924
/*
19341925
* Report query to various monitoring facilities.
@@ -2016,13 +2007,24 @@ exec_execute_message(const char *portal_name, long max_rows)
20162007

20172008
if (completed)
20182009
{
2019-
if (is_xact_command)
2010+
if (is_xact_command || (MyXactFlags & XACT_FLAGS_NEEDIMMEDIATECOMMIT))
20202011
{
20212012
/*
20222013
* If this was a transaction control statement, commit it. We
20232014
* will start a new xact command for the next command (if any).
2015+
* Likewise if the statement required immediate commit. Without
2016+
* this provision, we wouldn't force commit until Sync is
2017+
* received, which creates a hazard if the client tries to
2018+
* pipeline immediate-commit statements.
20242019
*/
20252020
finish_xact_command();
2021+
2022+
/*
2023+
* These commands typically don't have any parameters, and even if
2024+
* one did we couldn't print them now because the storage went
2025+
* away during finish_xact_command. So pretend there were none.
2026+
*/
2027+
portalParams = NULL;
20262028
}
20272029
else
20282030
{

src/include/access/xact.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ extern int MyXactFlags;
9898
*/
9999
#define XACT_FLAGS_ACCESSEDTEMPNAMESPACE (1U << 2)
100100

101+
/*
102+
* XACT_FLAGS_NEEDIMMEDIATECOMMIT - records whether the top level statement
103+
* is one that requires immediate commit, such as CREATE DATABASE.
104+
*/
105+
#define XACT_FLAGS_NEEDIMMEDIATECOMMIT (1U << 3)
106+
101107
/*
102108
* start- and end-of-transaction callbacks for dynamically loaded modules
103109
*/

0 commit comments

Comments
 (0)