Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 6c193c2

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/17434-d9f7a064ce2a88a3@postgresql.org
1 parent 4c9e516 commit 6c193c2

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
@@ -1050,6 +1050,64 @@ SELCT 1/0;<!-- this typo is intentional -->
10501050
</note>
10511051
</sect2>
10521052

1053+
<sect2 id="protocol-flow-pipelining">
1054+
<title>Pipelining</title>
1055+
1056+
<indexterm zone="protocol-flow-pipelining">
1057+
<primary>pipelining</primary>
1058+
<secondary>protocol specification</secondary>
1059+
</indexterm>
1060+
1061+
<para>
1062+
Use of the extended query protocol
1063+
allows <firstterm>pipelining</firstterm>, which means sending a series
1064+
of queries without waiting for earlier ones to complete. This reduces
1065+
the number of network round trips needed to complete a given series of
1066+
operations. However, the user must carefully consider the required
1067+
behavior if one of the steps fails, since later queries will already
1068+
be in flight to the server.
1069+
</para>
1070+
1071+
<para>
1072+
One way to deal with that is to make the whole query series be a
1073+
single transaction, that is wrap it in <command>BEGIN</command> ...
1074+
<command>COMMIT</command>. However, this does not help if one wishes
1075+
for some of the commands to commit independently of others.
1076+
</para>
1077+
1078+
<para>
1079+
The extended query protocol provides another way to manage this
1080+
concern, which is to omit sending Sync messages between steps that
1081+
are dependent. Since, after an error, the backend will skip command
1082+
messages until it finds Sync, this allows later commands in a pipeline
1083+
to be skipped automatically when an earlier one fails, without the
1084+
client having to manage that explicitly with <command>BEGIN</command>
1085+
and <command>COMMIT</command>. Independently-committable segments
1086+
of the pipeline can be separated by Sync messages.
1087+
</para>
1088+
1089+
<para>
1090+
If the client has not issued an explicit <command>BEGIN</command>,
1091+
then each Sync ordinarily causes an implicit <command>COMMIT</command>
1092+
if the preceding step(s) succeeded, or an
1093+
implicit <command>ROLLBACK</command> if they failed. However, there
1094+
are a few DDL commands (such as <command>CREATE DATABASE</command>)
1095+
that cannot be executed inside a transaction block. If one of
1096+
these is executed in a pipeline, it will, upon success, force an
1097+
immediate commit to preserve database consistency.
1098+
A Sync immediately following one of these has no effect except to
1099+
respond with ReadyForQuery.
1100+
</para>
1101+
1102+
<para>
1103+
When using this method, completion of the pipeline must be determined
1104+
by counting ReadyForQuery messages and waiting for that to reach the
1105+
number of Syncs sent. Counting command completion responses is
1106+
unreliable, since some of the commands may not be executed and thus not
1107+
produce a completion message.
1108+
</para>
1109+
</sect2>
1110+
10531111
<sect2>
10541112
<title>Function Call</title>
10551113

src/backend/access/transam/xact.c

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3370,6 +3370,9 @@ AbortCurrentTransaction(void)
33703370
* could issue more commands and possibly cause a failure after the statement
33713371
* completes). Subtransactions are verboten too.
33723372
*
3373+
* We must also set XACT_FLAGS_NEEDIMMEDIATECOMMIT in MyXactFlags, to ensure
3374+
* that postgres.c follows through by committing after the statement is done.
3375+
*
33733376
* isTopLevel: passed down from ProcessUtility to determine whether we are
33743377
* inside a function. (We will always fail if this is false, but it's
33753378
* convenient to centralize the check here instead of making callers do it.)
@@ -3411,7 +3414,9 @@ PreventInTransactionBlock(bool isTopLevel, const char *stmtType)
34113414
if (CurrentTransactionState->blockState != TBLOCK_DEFAULT &&
34123415
CurrentTransactionState->blockState != TBLOCK_STARTED)
34133416
elog(FATAL, "cannot prevent transaction chain");
3414-
/* all okay */
3417+
3418+
/* All okay. Set the flag to make sure the right thing happens later. */
3419+
MyXactFlags |= XACT_FLAGS_NEEDIMMEDIATECOMMIT;
34153420
}
34163421

34173422
/*
@@ -3508,6 +3513,13 @@ IsInTransactionBlock(bool isTopLevel)
35083513
CurrentTransactionState->blockState != TBLOCK_STARTED)
35093514
return true;
35103515

3516+
/*
3517+
* If we tell the caller we're not in a transaction block, then inform
3518+
* postgres.c that it had better commit when the statement is done.
3519+
* Otherwise our report could be a lie.
3520+
*/
3521+
MyXactFlags |= XACT_FLAGS_NEEDIMMEDIATECOMMIT;
3522+
35113523
return false;
35123524
}
35133525

src/backend/tcop/postgres.c

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1272,6 +1272,13 @@ exec_simple_query(const char *query_string)
12721272
}
12731273
else
12741274
{
1275+
/*
1276+
* We had better not see XACT_FLAGS_NEEDIMMEDIATECOMMIT set if
1277+
* we're not calling finish_xact_command(). (The implicit
1278+
* transaction block should have prevented it from getting set.)
1279+
*/
1280+
Assert(!(MyXactFlags & XACT_FLAGS_NEEDIMMEDIATECOMMIT));
1281+
12751282
/*
12761283
* We need a CommandCounterIncrement after every query, except
12771284
* those that start or end a transaction block.
@@ -2082,32 +2089,16 @@ exec_execute_message(const char *portal_name, long max_rows)
20822089

20832090
/*
20842091
* We must copy the sourceText and prepStmtName into MessageContext in
2085-
* case the portal is destroyed during finish_xact_command. Can avoid the
2086-
* copy if it's not an xact command, though.
2092+
* case the portal is destroyed during finish_xact_command. We do not
2093+
* make a copy of the portalParams though, preferring to just not print
2094+
* them in that case.
20872095
*/
2088-
if (is_xact_command)
2089-
{
2090-
sourceText = pstrdup(portal->sourceText);
2091-
if (portal->prepStmtName)
2092-
prepStmtName = pstrdup(portal->prepStmtName);
2093-
else
2094-
prepStmtName = "<unnamed>";
2095-
2096-
/*
2097-
* An xact command shouldn't have any parameters, which is a good
2098-
* thing because they wouldn't be around after finish_xact_command.
2099-
*/
2100-
portalParams = NULL;
2101-
}
2096+
sourceText = pstrdup(portal->sourceText);
2097+
if (portal->prepStmtName)
2098+
prepStmtName = pstrdup(portal->prepStmtName);
21022099
else
2103-
{
2104-
sourceText = portal->sourceText;
2105-
if (portal->prepStmtName)
2106-
prepStmtName = portal->prepStmtName;
2107-
else
2108-
prepStmtName = "<unnamed>";
2109-
portalParams = portal->portalParams;
2110-
}
2100+
prepStmtName = "<unnamed>";
2101+
portalParams = portal->portalParams;
21112102

21122103
/*
21132104
* Report query to various monitoring facilities.
@@ -2206,13 +2197,24 @@ exec_execute_message(const char *portal_name, long max_rows)
22062197

22072198
if (completed)
22082199
{
2209-
if (is_xact_command)
2200+
if (is_xact_command || (MyXactFlags & XACT_FLAGS_NEEDIMMEDIATECOMMIT))
22102201
{
22112202
/*
22122203
* If this was a transaction control statement, commit it. We
22132204
* will start a new xact command for the next command (if any).
2205+
* Likewise if the statement required immediate commit. Without
2206+
* this provision, we wouldn't force commit until Sync is
2207+
* received, which creates a hazard if the client tries to
2208+
* pipeline immediate-commit statements.
22142209
*/
22152210
finish_xact_command();
2211+
2212+
/*
2213+
* These commands typically don't have any parameters, and even if
2214+
* one did we couldn't print them now because the storage went
2215+
* away during finish_xact_command. So pretend there were none.
2216+
*/
2217+
portalParams = NULL;
22162218
}
22172219
else
22182220
{

src/include/access/xact.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,12 @@ extern int MyXactFlags;
103103
*/
104104
#define XACT_FLAGS_ACQUIREDACCESSEXCLUSIVELOCK (1U << 1)
105105

106+
/*
107+
* XACT_FLAGS_NEEDIMMEDIATECOMMIT - records whether the top level statement
108+
* is one that requires immediate commit, such as CREATE DATABASE.
109+
*/
110+
#define XACT_FLAGS_NEEDIMMEDIATECOMMIT (1U << 2)
111+
106112
/*
107113
* start- and end-of-transaction callbacks for dynamically loaded modules
108114
*/

0 commit comments

Comments
 (0)