aboutsummaryrefslogtreecommitdiff
path: root/src/backend/tcop/postgres.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2022-07-26 13:07:03 -0400
committerTom Lane <tgl@sss.pgh.pa.us>2022-07-26 13:07:03 -0400
commitf92944137cdec3e80e826879d817a2d3dff68b5f (patch)
tree95d3fb5010838838ccb4fe545bd73c3c05a47de2 /src/backend/tcop/postgres.c
parent3cabe45a819f8a2a282d9d57e45f259c84e97c3f (diff)
downloadpostgresql-f92944137cdec3e80e826879d817a2d3dff68b5f.tar.gz
postgresql-f92944137cdec3e80e826879d817a2d3dff68b5f.zip
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
Diffstat (limited to 'src/backend/tcop/postgres.c')
-rw-r--r--src/backend/tcop/postgres.c52
1 files changed, 27 insertions, 25 deletions
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index d0bbd30d2b5..078fbdb5a0c 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -1278,6 +1278,13 @@ exec_simple_query(const char *query_string)
else
{
/*
+ * We had better not see XACT_FLAGS_NEEDIMMEDIATECOMMIT set if
+ * we're not calling finish_xact_command(). (The implicit
+ * transaction block should have prevented it from getting set.)
+ */
+ Assert(!(MyXactFlags & XACT_FLAGS_NEEDIMMEDIATECOMMIT));
+
+ /*
* We need a CommandCounterIncrement after every query, except
* those that start or end a transaction block.
*/
@@ -2092,32 +2099,16 @@ exec_execute_message(const char *portal_name, long max_rows)
/*
* We must copy the sourceText and prepStmtName into MessageContext in
- * case the portal is destroyed during finish_xact_command. Can avoid the
- * copy if it's not an xact command, though.
+ * case the portal is destroyed during finish_xact_command. We do not
+ * make a copy of the portalParams though, preferring to just not print
+ * them in that case.
*/
- if (is_xact_command)
- {
- sourceText = pstrdup(portal->sourceText);
- if (portal->prepStmtName)
- prepStmtName = pstrdup(portal->prepStmtName);
- else
- prepStmtName = "<unnamed>";
-
- /*
- * An xact command shouldn't have any parameters, which is a good
- * thing because they wouldn't be around after finish_xact_command.
- */
- portalParams = NULL;
- }
+ sourceText = pstrdup(portal->sourceText);
+ if (portal->prepStmtName)
+ prepStmtName = pstrdup(portal->prepStmtName);
else
- {
- sourceText = portal->sourceText;
- if (portal->prepStmtName)
- prepStmtName = portal->prepStmtName;
- else
- prepStmtName = "<unnamed>";
- portalParams = portal->portalParams;
- }
+ prepStmtName = "<unnamed>";
+ portalParams = portal->portalParams;
/*
* Report query to various monitoring facilities.
@@ -2216,13 +2207,24 @@ exec_execute_message(const char *portal_name, long max_rows)
if (completed)
{
- if (is_xact_command)
+ if (is_xact_command || (MyXactFlags & XACT_FLAGS_NEEDIMMEDIATECOMMIT))
{
/*
* If this was a transaction control statement, commit it. We
* will start a new xact command for the next command (if any).
+ * Likewise if the statement required immediate commit. Without
+ * this provision, we wouldn't force commit until Sync is
+ * received, which creates a hazard if the client tries to
+ * pipeline immediate-commit statements.
*/
finish_xact_command();
+
+ /*
+ * These commands typically don't have any parameters, and even if
+ * one did we couldn't print them now because the storage went
+ * away during finish_xact_command. So pretend there were none.
+ */
+ portalParams = NULL;
}
else
{