From d8bd18f4f6c551449f7fcf993d646a0b3cce28ef Mon Sep 17 00:00:00 2001
From: Andrey Borodin <amborodin@acm.org>
Date: Fri, 2 May 2025 17:02:11 +0500
Subject: [PATCH v2 2/3] Do not allow to cancel locally written transaction

This extends startup_synchronous_commit on locally logged transactions.
If transaction is not replicated and startup synchronisation is
configured, we do not allow transaction cancelation.

With extending GUC scope we also rename it to
failover_synchronous_standby_level.
---
 src/backend/access/transam/xact.c             |  2 +-
 src/backend/replication/syncrep.c             | 16 +++++++++++-----
 src/backend/utils/init/postinit.c             |  2 +-
 src/backend/utils/misc/guc_tables.c           |  4 ++--
 src/backend/utils/misc/postgresql.conf.sample |  1 +
 src/include/access/xact.h                     |  2 +-
 6 files changed, 17 insertions(+), 10 deletions(-)

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 8962b311361..a2011e80b40 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -85,7 +85,7 @@ bool		DefaultXactDeferrable = false;
 bool		XactDeferrable;
 
 int			synchronous_commit = SYNCHRONOUS_COMMIT_ON;
-int			startup_synchronous_commit = SYNCHRONOUS_COMMIT_OFF;
+int			failover_synchronous_standby_level = SYNCHRONOUS_COMMIT_OFF;
 
 /*
  * CheckXidAlive is a xid value pointing to a possibly ongoing (sub)
diff --git a/src/backend/replication/syncrep.c b/src/backend/replication/syncrep.c
index 4ed1d582a11..5c35fc419e3 100644
--- a/src/backend/replication/syncrep.c
+++ b/src/backend/replication/syncrep.c
@@ -318,11 +318,17 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit)
 		if (QueryCancelPending)
 		{
 			QueryCancelPending = false;
-			ereport(WARNING,
-					(errmsg("canceling wait for synchronous replication due to user request"),
-					 errdetail("The transaction has already committed locally, but might not have been replicated to the standby.")));
+			if (failover_synchronous_standby_level < SYNCHRONOUS_COMMIT_REMOTE_WRITE)
+			{
+				ereport(WARNING,
+						(errmsg("canceling wait for synchronous replication due to user request"),
+						 errdetail("The transaction has already committed locally, but might not have been replicated to the standby.")));
 			SyncRepCancelWait();
 			break;
+			}
+			ereport(WARNING,
+				(errmsg("user requested cancel of waiting for synchronous replication"),
+				 errdetail("The transaction has already committed locally and cannot be rolled back, but have not been replicated according to synchronous_standby_names.")));
 		}
 
 		/*
@@ -1151,7 +1157,7 @@ StartupSyncRepEstablished(void)
 	int mode;
 	bool result;
 
-	switch (startup_synchronous_commit)
+	switch (failover_synchronous_standby_level)
 	{
 		case SYNCHRONOUS_COMMIT_REMOTE_WRITE:
 			mode = SYNC_REP_WAIT_WRITE;
@@ -1163,7 +1169,7 @@ StartupSyncRepEstablished(void)
 			mode = SYNC_REP_WAIT_APPLY;
 			break;
 		default:
-		/* If startup_synchronous_commit is not set to a synchronous level, no need to check */
+		/* If failover_synchronous_standby_level is not set to a synchronous level, no need to check */
 			return true;
 	}
 
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 10b354fe069..cd84ddab0ae 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -1222,7 +1222,7 @@ InitPostgres(const char *in_dbname, Oid dboid,
 	{
 		ereport(FATAL,
 				(errcode(ERRCODE_CANNOT_CONNECT_NOW),
-				 errmsg("cannot connect until synchronous replication is established with standbys according to startup_synchronous_standby_level")));
+				 errmsg("cannot connect until synchronous replication is established with standbys according to failover_synchronous_standby_level")));
 	}
 
 	/*
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 23e20261013..a95fbb1efe6 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -5174,11 +5174,11 @@ struct config_enum ConfigureNamesEnum[] =
 	},
 
 	{
-		{"startup_synchronous_standby_level", PGC_SUSET, REPLICATION_PRIMARY,
+		{"failover_synchronous_standby_level", PGC_SUSET, REPLICATION_PRIMARY,
 			gettext_noop("Sets the synchronization level neccesary to start node."),
 			NULL
 		},
-		&startup_synchronous_commit,
+		&failover_synchronous_standby_level,
 		SYNCHRONOUS_COMMIT_OFF, synchronous_commit_options,
 		NULL, NULL, NULL
 	},
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 34826d01380..608d517f198 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -239,6 +239,7 @@
 					# unrecoverable data corruption)
 #synchronous_commit = on		# synchronization level;
 					# off, local, remote_write, remote_apply, or on
+#failover_synchronous_standby_level = of	# same levels as for synchronous_commit
 #wal_sync_method = fsync		# the default is the first option
 					# supported by the operating system:
 					#   open_datasync
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index feb47c80dfe..b29a8a94156 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -81,7 +81,7 @@ typedef enum
 
 /* Synchronous commit level */
 extern PGDLLIMPORT int synchronous_commit;
-extern PGDLLIMPORT int startup_synchronous_commit;
+extern PGDLLIMPORT int failover_synchronous_standby_level;
 
 /* used during logical streaming of a transaction */
 extern PGDLLIMPORT TransactionId CheckXidAlive;
-- 
2.39.5 (Apple Git-154)

